前端學習(35)~js學習(十二):預編譯
- 2020 年 3 月 18 日
- 筆記
我們在上一篇文章《作用域》中簡單講過「變量提升」,今天來講一下預編譯,這對我們深入理解變量提升會有幫助。
JavaScript 運行三部曲
- 語法分析
- 預編譯
- 解釋執行
預編譯前奏
在講預編譯前,我們先來普及下面兩個規律。
兩個規律
規律1:任何變量,如果未經聲明就賦值,此變量是屬於 window 的屬性,而且不會做變量提升。(注意,無論在哪個作用域內賦值)
比如說,如果我們直接在代碼里寫 console.log(a)
,這肯定會報錯的,提示找不到 a
。但如果我直接寫 a = 100
,這就不會報錯,此時,這個 a
就是 window.a
。
規律2:一切聲明的全局變量,全是window的屬性。(注意,我說的是在全局作用域內聲明的全局變量,不是說局部變量)
比如說,當我定義 var a = 200
時,這此時這個 a
就是 window.a
。
由此,我們可以看出:window 代表了全局作用域(是說「代表」,沒說「等於」)。
舉例
掌握了上面兩句話之後,我們再來看看下面的例子。
function foo() { var a = b = 100; // 連續賦值 } foo(); console.log(window.b); // 在全局範圍內訪問 b console.log(b); // 在全局範圍內訪問 b,但是前面沒有加 window 這個關鍵字 console.log(window.a); // 在全局範圍內訪問 a console.log(a); // 在全局範圍內訪問 a,但是前面沒有加 window 這個關鍵字
上方代碼的打印結果:
100 100 undefined (會報錯,提示 Uncaught ReferenceError: a is not defined)
解釋:
當執行了foo()
函數之後, var a = b = 100
這行連續賦值的代碼等價於 var a = (b = 100)
,其執行順序是:
(1)先把 100 賦值給 b;
(2)再聲明變量 a;
(3)再把 b 的值賦值給 a。
我們可以看到,b 是未經聲明的變量就被賦值了,此時,根據規律1,這個 b 是屬於 window.b
;而 a 的作用域僅限於 foo() 函數內部,不屬於 window。所以也就有了這樣的打印結果。
預編譯
函數預編譯的步驟
函數預編譯,發生在函數執行的前一刻。
(1)創建AO對象。AO即 Activation Object 活躍對象,其實就是「執行期上下文」。
(2)找形參和變量聲明,將形參名和變量作為 AO 的屬性名,值為undefined。
(3)將實參值和形參統一,實參的值賦給形參。
(4)查找函數聲明,函數名作為 AO 對象的屬性名,值為整個函數體。
這個地方比較難理解。但只有了解了函數的預編譯,才能理解明白函數的執行順序。
代碼舉例:
function fn(a) { console.log(a); var a = 666; console.log(a); function a() {} console.log(a); var b = function() {}; console.log(b); function c() {} } fn(1);
打印結果:
ƒ a() {} 666 666 ƒ () {}