前言
JS屬於解釋型語言,在執行過程中順序執行,但是會分塊先預編譯然後才執行。因此在JS中存在一種變數提升的現象。搞懂預編譯環節,變數提升自然而然也就懂了。本文講圍繞以下幾點進行介紹(變數提升會穿插在其中講解):
- 預編譯執行步驟
- 示例演示
預編譯執行步驟
預編譯發生在函式執行的前一刻,過程如下:
- 建立AO物件,執行期上下文(後面更新關於執行期上下文詳解)。
- 尋找函式的形參和變數宣告,將變數和形參名作為AO物件的屬性名,值設定為undefined.
- 將形參和實參相統一,即更改形參後的undefined為具體的形參值。
- 尋找函式中的函式宣告,將函式名作為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);
複製程式碼
接下來我們來按照前面的步驟詳細分析它的預編譯執行過程:
- 建立AO物件
AO{
//空物件
}
複製程式碼
- 找形參和變數宣告
AO{
a : undefined,
b : undefined
}
複製程式碼
- 形參和實參相統一
AO{
a : 1,
b : function(){}
}
複製程式碼
- 找函式宣告
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
複製程式碼