Javascript的預解析機制
JS的預解析機制
想要理解js的解析機制,不妨先了解兩個概念:變數宣告提升與函式宣告提升。
宣告提升:函式宣告和變數宣告總是會被直譯器悄悄地被"提升"到方法體的最頂部。
變數宣告提升
JavaScript 中,變數可以在使用後宣告,也就是變數可以先使用再宣告。
以下兩個例項將得到同樣的結果:
//先使用後宣告
x = 5; // 變數 x 設定為 5
elem = document.getElementById("demo"); // 查詢元素
elem.innerHTML = x; // 在元素中顯示 x
var x; // 宣告 x
//先聲明後使用 var x; // 宣告 x x = 5; // 變數 x 設定為 5 elem = document.getElementById("demo"); // 查詢元素 elem.innerHTML = x; // 在元素中顯示 x
但是要注意JavaScript 只有宣告的變數會提升,初始化的不會。
例項1:
var x = 8; // 初始化 x
var y = 9; // 初始化 y
elem = document.getElementById("demo"); // 查詢元素
elem.innerHTML = x + " " + y; // 顯示 x 和
例項2:
var x = 5; // 初始化 x elem = document.getElementById("demo"); // 查詢元素 elem.innerHTML = x + " " + y; // 顯示 x 和 y var y = 7; // 初始化 y
我們可以看到例項 2 的 y 輸出了 undefined,這是因為變數宣告 (var y
) 提升了,但是初始化(y = 7
) 並不會提升,所以 y 變數是一個未定義的變數。
函式宣告提升
需要注意都是函式宣告提升直接把整個函式提到執行環境的最頂端。
a=5;
show();
var a;
function show(){};
預解析
function show(){};
var a;
a=5;
show();
除了以上的函式宣告方式外,還可以使用匿名函式的方式。
宣告:
var 變數名稱=function(形參列表){
//函式體
}
呼叫:
變數名稱(實參列表)
注意:使用匿名函式的方式不存在函式提升,因為函式名稱使用變量表示的,只存在變數提升。例:
var getName=function(){
console.log(2);
}
function getName(){
console.log(1);
}
getName();
//結果為2
getName 是一個變數,因此這個變數的宣告也將提升到頂部,而變數的賦值依然保留在原來的位置。需要注意的是,函式優先,雖然函式宣告和變數宣告都會被提升,但是函式會首先被提升,然後才是變數。
//函式、變數宣告提升後
function getName(){ //函式宣告提升到頂部
console.log(1);
}
var getName; //變數宣告提升
getName = function(){ //變數賦值依然保留在原來的位置
console.log(2);
}
getName(); // 最終輸出:2
預解析過程
預解析過程也就是建立 AO(Activation Object) 的過程。
建立AO過程:
- 建立 AO 物件。
- 將形參和函式內變數宣告作為物件的屬性名,屬性值統一為 undefined。
- 將實參賦值給形參。
- 找函式內的函式宣告作為物件的屬性名,屬性值為函式體。
接下來以一個例子來解釋一下這樣一個過程:
1 function jsFun6(){ //函式宣告和函式表示式的區別
2
3 test1();//函式宣告提升,在執行程式碼之前會先讀取函式宣告,不會報錯
4 function test1(){//函式宣告方式建立函式
5 alert("測試1");
6 }
7
8 //test2();報錯,函式還不存在
9 console.log(test2)//不會報錯,變數提升只是提升變數的宣告,並不會把賦值也提升上來,輸出undefined
10 var test2=function(){
11 alert("測試2");
12 };//使用函式表示式建立一個匿名函式(實際是以變數test3命名的函式)
13 test2();//不會報錯,以建立函式
14
15 var test3=function(){
16 alert("測試3");
17 }();//加了括號立即執行
18
19 var test4 = 12;// !注意看,一旦變數被賦值後,將會輸出變數
20 //函式提升優先順序高於變數提升,所以函式先提升,然後變數提升覆蓋之前的函式宣告,表
21 //現為變數
22 function test4() {
23 alert("測試4");
24 }
25 console.log(test4); //12
26
27 var test5="test5_1";
28 (function(){
29 //js中的變數搜尋順序:找變數時,先找區域性變數,如果沒有區域性變數;再找全域性變數。
30 alert(test5);//此時的test5為區域性變數的提升,undefined
31 var test5="test5_2";
32 })();
33
34 }
35 jsFun6();
首先 JS 在執行之前會有一個預編譯過程,變數提升和函式提升就是發生在這裡,我們按照步驟來分析一下以上程式碼:
step1
在執行 jsFun6 函式時首先會建立一個 AO 物件(Activetion Object 執行期上下文)。
AO{
}
step2
然後會將形參和變數宣告作為 AO 物件的屬性名,屬性的值為 undefined。
我們可以看到 jsFun6 函式裡沒有形參,但是有以下變數宣告:
第10行的 var test2
第15行的 var test3
第19行的 var test4
第27行的 var test5
所以 AO 物件現在是這樣:
AO{
test2:undefined
test3:undefined
test4:undefined
test5:undefined
}
step3
然後會將實參的值傳遞給形參(當前沒有形參所以略過)。
step4
再找到函式宣告作為AO的屬性名,屬性的值為函式宣告的函式體(這裡就是函式宣告提升)。
我們可以看到 jsFun6 函式裡有以下的函式宣告:
第4行 function test1(){}
第22行 function test4(){}
注意:這裡的第 10 行和第 15 行是函式表示式不是函式宣告。
所以 AO 物件現在是這樣:
AO{
test2:undefined,
test3:undefined,
test4:function test4(){alert("測試4");},
test5:undefined,
test1:function test1(){alert("測試1");}
}
這裡需要注意因為之前 AO 物件裡已經有 test4 屬性了所以 AO 物件裡原來由變數宣告時得到的 test4 屬性會被現在函式宣告的 test4 覆蓋。
到這裡預編譯就完成了開始執行。
step5:執行
執行時會先去查詢 AO 物件,如果沒找到就會去 GO(Global Object)物件裡查詢(AO 物件相當於區域性變數,GO 物件相當於全域性變數)。
當執行第 3 行時在 AO 裡找到 test1 執行,彈出提示框顯示提示資訊“測試1”。
第 4 行已經提升了所以可以略過,下面其他的類似行與此相同。
執行第 8 行時, 在 AO 裡找到 test2,並作為函式執行,由於 AO 裡的 test2 的值是 undefined 所以執行會報錯。
第 9 行輸出 test2 所以輸出的是 undefined。
第 10 行將函式體賦值給 test2,當前 AO 變成:
AO{
test2:function(){alert("測試2");},
test3:undefined,
test4:function test4(){alert("測試4");},
test5:undefined,
test1:function test1(){alert("測試1");}
}
所以第 13 行執行時在 AO 裡能找到 test2 並作為函式執行,彈出提示框顯示提示資訊“測試2”。
第 15 行由於用於賦值的函式體後面加了 () 變成立即執行函式所以函式會立即執行(這裡會彈出提示框顯示提示資訊“測試3”)然後將返回值賦值給 test3,由於函式沒有返回值所以是 undefined,也就導致了 AO 裡的 test3 的屬性值還是 undefined(大家可以在第18行輸出一下 test3,會發現還是輸出 undefined)。
第 19 行將 12 賦值給 test4,將原來 test4 的屬性值覆蓋了,AO 物件變為:
AO{
test2:function(){alert("測試2");},
test3:undefined,
test4:12,
test5:undefined,
test1:function test1(){alert("測試1");}
}
所以在第 25 行會輸出 12(如果在這裡再把 test4 作為函式去執行就會報錯)。
第 27 行將 test5_1 賦值給 test5。
第 28 行是立即執行函式,會立即執行。
在執行之前會進行預編譯所以這個函式又會建立一個 屬於他自己的 AO 物件,為了區分我們將他標識為 AO2。
AO2{
}
然後將形參和變數宣告作為 AO 物件的屬性名,屬性的值為 undefined。
AO2{
test5:undefined
}
然後進行實參形參統一,以及函式宣告提升(由於沒有所以略過)。
執行第 30 行時在 AO2 裡找到了 test5,屬性值為 undefined。
所以會彈出提示框顯示提示資訊 “undefined”。
執行第 31 行是將 test5_2 賦值給 test5。
所以 AO2 變成了:
AO2{
test5:test5_2
}