1. 程式人生 > 實用技巧 >JavaScript預編譯

JavaScript預編譯

在JavaScript中存在一種預編譯的機制,這也是Java等一些語言中沒有的特性,也就正是因為這個預編譯的機制,導致了js中變數提升的一些問題,下面這兩句話能解決開發當中一部份問題,但不能解決所有問題,還有一些問題是你必須通過學習預編譯才能解決的。

函式宣告整體提升

變數宣告提升(*注意是變數宣告)
tip:JS函式的呼叫永遠都是在函式宣告下面呼叫,即使你的呼叫是寫在函式宣告之前的,它隱式也是在函式宣告下呼叫的。


預編譯什麼時候發生

預編譯分為全域性預編譯和區域性預編譯,全域性預編譯發生在頁面載入完成時執行,而區域性預編譯發生在函式執行的前一刻。

tip:預編譯階段發生變數宣告和函式宣告,沒有初始化行為(賦值),匿名函式不參與預編譯 。只有在解釋執行階段才會進行變數初始化 。

js執行三步曲

  1. 語法分析
  2. 預編譯
  3. 解釋執行

預編譯前奏

imply global暗示全域性變數,任何變數,如果變數未經宣告就賦值,這些變數就為全域性物件所有。一切宣告的全域性變數和未經宣告的變數,全歸window所有。

例如:

var a = 123;
window.a = 123;

下面這個函式裡面只有一個連等的操作,賦值操作都是自右向左的,而b是未經宣告的變數,所以它是歸window的,我們可以直接使用window.b去使用它。

function test(){
    // 這裡的b是未經宣告的變數,所以是歸window所有的。
    var a = b = 110;
}

預編譯步驟

首先JavaScript的執行過程會先掃描一下整體語法語句,如果存在邏輯錯誤或者語法錯誤,那麼直接報錯,程式停止執行,沒有錯誤的話,開始從上到下解釋一行執行一行。

區域性預編譯的4個步驟:

  1. 建立AO物件(Activation Object)執行期上下文。
  2. 找形參和變數宣告,將變數和形參名作為AO屬性名,值為undefined
  3. 將實參值和形參統一。
  4. 在函式體裡面找函式宣告,值賦予函式體。

全域性預編譯的3個步驟:

  1. 建立GO物件(Global Object)全域性物件。
  2. 找變數宣告,將變數名作為GO屬性名,值為undefined
  3. 查詢函式宣告,作為GO屬性,值賦予函式體

由於全域性中沒有引數的的概念,所以省去了實參形參相統一這一步。
tip:GO物件是全域性預編譯,所以它優先於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.預編譯第1步:建立AO物件

AO{ 

}

2.預編譯第2步:找形參和變數宣告,將形參名和變數名作為AO物件的屬性名

AO{
     a : undefined,
     b : undefined
}

3.預編譯第3步:將實參值和形參統一

AO{
     a : 1,
     b : function(){...}
}

4.預編譯第4步:在函式體裡面找函式宣告,值賦予函式體。

AO{
     a : function a(){...},
     b : undefined,
     d : function d(){...}
}

最後輸出結果:

// 函式
function fn(a){
        console.log(a);         //根據AO物件中的資料第一個列印的是:fn()
    // 變數宣告+變數賦值(只提升變數宣告,不提升變數賦值)
    var a = 123;        // 執行到這時,由於變數賦值是不提升的,所以函式被123覆蓋了
    console.log(a);        // 123
    // 函式宣告
    function a(){};        // 這裡被提升上去了,可以忽略
    console.log(a);        // 123
    // 函式表示式
    var b = function(){};
    console.log(b);        // 根據AO物件中的資料:fn()
    // 函式
     function d(){};
}
//呼叫函式
fn(1);

關於GO物件的例子

global = 100;
function test(){
    console.log(global);    
    var global = 200;
    console.log(global);
    var global = 300;
}
test();
var global;

1.全域性預編譯第1步:建立GO物件

GO{

}

2.全域性預編譯第2步:找變數宣告,將變數名作為GO屬性名,值為undefined

GO{
    global:undefined
}

3.全域性預編譯第3步:查詢函式宣告,作為GO屬性,值賦予函式體

GO{
global:undefined
}

4.區域性預編譯第1步:建立AO物件

AO{
      
}

5.區域性預編譯第2步:找形參和變數宣告,將形參名和變數名作為AO物件的屬性名

AO{
     global: undefined
}

6.區域性預編譯第3步:將實參值和形參統一(此函式沒有形參)

AO{
     global: undefined
}

7.區域性預編譯第4步:在函式體裡面找函式宣告,值賦予函式體。(此函式內沒有函式宣告)

AO{
     global: undefined
}

最的結果:

global = 100;
function test(){
    console.log(global);        // 根據AO物件中的資料:undefined
    var global = 200;        // 執行到這時,200覆蓋了undefined
    console.log(global);        // 200
    var global = 300;
}
test();
var global;

tip:關於GO物件和AO物件,它們倆是一個種鏈式關係,就拿上面的這個例子來說吧,如果在函式體的內部沒有定義global變數,這也意味著AO物件中將有這個global這個屬性。那如果沒有會怎麼辦?它會去GO物件中尋找,說白了也就是一種就近原則