JS----預編譯及變數提升詳解

北海北方發表於2018-03-13

前言

JS屬於解釋型語言,在執行過程中順序執行,但是會分塊先預編譯然後才執行。因此在JS中存在一種變數提升的現象。搞懂預編譯環節,變數提升自然而然也就懂了。本文講圍繞以下幾點進行介紹(變數提升會穿插在其中講解):

  • 預編譯執行步驟
  • 示例演示

預編譯執行步驟

預編譯發生在函式執行的前一刻,過程如下:

  1. 建立AO物件,執行期上下文(後面更新關於執行期上下文詳解)。
  2. 尋找函式的形參和變數宣告,將變數和形參名作為AO物件的屬性名,值設定為undefined.
  3. 將形參和實參相統一,即更改形參後的undefined為具體的形參值。
  4. 尋找函式中的函式宣告,將函式名作為AO屬性名,值為函式體。

至此,預編譯環節結束,函式中咯變數按照最終AO物件中的值開始執行。接下來,結合示例演示就會更加清晰。

示例演示

我們先來看下面這段程式碼:

 function fn(a){
    console.log(a);
    var a = 123;
    console.log(a);
    
    function a(){};
    console.log(a);
    
    var b = function(){};
    console.log(b);
    
    function d(){};
 }
 
 //呼叫函式
 fn(1);
複製程式碼

接下來我們來按照前面的步驟詳細分析它的預編譯執行過程:

  1. 建立AO物件
AO{
    //空物件    
}
複製程式碼
  1. 找形參和變數宣告
AO{
    a : undefined,
    b : undefined
}
複製程式碼
  1. 形參和實參相統一
AO{
    a : 1,
    b : function(){}
}
複製程式碼
  1. 找函式宣告
AO{
    a : function a(){},
    b : undefined,
    d : function d(){}
}
複製程式碼

預編譯環節就此結束,此時的AO物件已經更新為:

AO{
    a : function a(){},
    b : undefined,
    d : function d(){}
}
複製程式碼

函式開始逐行順序執行:

 function fn(a){
    console.log(a);// 輸出functiona(){}
    var a = 123;//執行到這裡重新對a賦,AO物件再一次更新
    console.log(a);// 輸出123
    
    function a(){};//預編譯環節已經進行了變數提升,故執行時不在看這行程式碼
    console.log(a);// 輸出123
    
    var b = function(){};//這個是函式表示式不是函式宣告,故不能提升,會對AO中的b重新賦值
    console.log(b);//輸出function(){}
    
    function d(){};
 }
複製程式碼

至此,函式執行完畢,銷燬AO物件。

我們再來看幾個例子,熟悉函式的預編譯過程。

示例一:

function test (a,b){
    console.log(a);
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b);
    function b(){};
    function d(){};
    console.log(b);
} 
//呼叫函式
test(1);
複製程式碼

它的AO建立過程如下(此處省略建立空AO物件的部分,下文同):

AO1{
    a : undefined,
    b : undefined,
    c : undefined
}

AO2{
    a : 1,
    b : undefined,
    c : undefined
}

AO3{
    a : 1,
    b : function b(){},
    c : undefined,
    d : function d(){}
}
複製程式碼

至此預編譯環節完成,開始執行:

function test (a,b){
    console.log(a); //輸出1
    c = 0; //給AO物件中的c重新賦值0
    var c;//預編譯環節變數提升,不再讀此行程式碼
    a = 3;//給AO物件中的a重新賦值3
    b = 2;//給AO物件中的b重新賦值2
    console.log(b);//輸出2
    function b(){};//預編譯環節變數提升,執行時不再讀這行程式碼
    function d(){};//預編譯環節變數提升,執行時不再讀這行程式碼
    console.log(b);//輸出2
} 
//呼叫函式
test(1);
複製程式碼

示例二:

這個例子中我們引入全域性物件GO。GO與AO的過程類似

function test(){
    var a = b = 123;
}
test();
複製程式碼

此函式的執行過程:先把123賦給b,再宣告a,再把b賦給a。此時變數b未經宣告就賦值,為全域性變數。預編譯環節如下:

GO1{
    b : undefined
}
AO1{
    a : undefined
}


GO2{
    b : 123;
}
AO2{
    a : 123;
}
複製程式碼

示例三 :

console.log(test);
function test(test){
   console.log(test);
   var test = 234;
   console.log(test);
   function test(){};
}
test(1);
var test = 123;
複製程式碼

我們來看它的預編譯過程:

//執行前(頁面載入完成時)生成GO物件
GO1{
    test : undefined
}
GO2{
    test : function(){}
}

//輸出 function test(){...}

//執行test()前生成它的AO物件
AO1{
    test : undefined
}
AO2{
    test : 1
}
AO3{
    test : function test(){}
}

//預編譯結束開始執行test(1);
AO4{
    test : 234
}
//輸出234
複製程式碼

示例四:

function demo(){
    console.log(b);
    if(a){
        var b = 100;
    }
    console.log(b);
    c = 234;
    console.log(c);
}
var a;
demo();
a = 10;
console.log(c);
複製程式碼

我們來看它的預編譯過程:

//首先是全域性物件GO 
GO1{
    a : undefined
}
G02{
    a : undefined,
    demo : function demo(){}
}
//執行demo()前預編譯,由於demo中的c未宣告就使用故為全域性物件

//輸出undefined
GO3{
    a : undefined,
    demo : function demo(){}
    c : undefined
}
//此時a還是undefined,故不執行if()程式碼塊
//輸出還是undefined
GO4{
    a : undefined,
    demo : function demo(){}
    c : 234;
}
//輸出234
GO5{
    a : 10,
    demo : function demo(){}
    c : 234;
}
//輸出234
複製程式碼

相關文章