前端學習(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  ƒ () {}