JavaScript預編譯
在JavaScript中存在一種預編譯的機制,這也是Java等一些語言中沒有的特性,也就正是因為這個預編譯的機制,導致了js中變數提升的一些問題,下面這兩句話能解決開發當中一部份問題,但不能解決所有問題,還有一些問題是你必須通過學習預編譯才能解決的。
函式宣告整體提升
變數宣告提升(*注意是變數宣告)
tip:JS函式的呼叫永遠都是在函式宣告下面呼叫,即使你的呼叫是寫在函式宣告之前的,它隱式也是在函式宣告下呼叫的。
預編譯什麼時候發生
預編譯分為全域性預編譯和區域性預編譯,全域性預編譯發生在頁面載入完成時執行,而區域性預編譯發生在函式執行的前一刻。
tip:預編譯階段發生變數宣告和函式宣告,沒有初始化行為(賦值),匿名函式不參與預編譯 。只有在解釋執行階段才會進行變數初始化 。
js執行三步曲
- 語法分析
- 預編譯
- 解釋執行
預編譯前奏
imply global暗示全域性變數,任何變數,如果變數未經宣告就賦值,這些變數就為全域性物件所有。一切宣告的全域性變數和未經宣告的變數,全歸window所有。
例如:
var a = 123; window.a = 123;
下面這個函式裡面只有一個連等的操作,賦值操作都是自右向左的,而b是未經宣告的變數,所以它是歸window的,我們可以直接使用window.b去使用它。
function test(){ // 這裡的b是未經宣告的變數,所以是歸window所有的。 var a = b = 110; }
預編譯步驟
首先JavaScript的執行過程會先掃描一下整體語法語句,如果存在邏輯錯誤或者語法錯誤,那麼直接報錯,程式停止執行,沒有錯誤的話,開始從上到下解釋一行執行一行。
區域性預編譯的4個步驟:
- 建立AO物件(Activation Object)執行期上下文。
- 找形參和變數宣告,將變數和形參名作為AO屬性名,值為undefined
- 將實參值和形參統一。
- 在函式體裡面找函式宣告,值賦予函式體。
全域性預編譯的3個步驟:
- 建立GO物件(Global Object)全域性物件。
- 找變數宣告,將變數名作為GO屬性名,值為undefined
- 查詢函式宣告,作為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物件中尋找,說白了也就是一種就近原則