1. 程式人生 > 其它 >Javascript的預解析機制

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
}