1. 程式人生 > 實用技巧 >函式提升和變數提升的理解

函式提升和變數提升的理解

變數提升
問題的產生
下面我們來看兩個例子
1.

a = 2;
var a;
console.log(a)

這裡你可能會認為輸入的結果是undefined,因為 var a 宣告在 a = 2 之後,會把之前的宣告覆蓋掉,所以結果是 undefined。但實際上列印的結果會是2
2.

console.log(a);
var a = 2;

鑑於上一個程式碼片段所表現出來的某種非自上而下的行為特點,你可能會認為這個程式碼片段也會有同樣的行為而輸出 2。還有人可能會認為,由於變數 a 在使用前沒有先進行宣告,因此會丟擲 ReferenceError 異常。
不幸的是兩種猜測都是不對的。輸出來的會是 undefined。
分析
分析之前需要了解的知識點——編譯原理。
想要深入理解這個問題出現的原因,我們必須對編譯的過程有所瞭解。
如果你曾經瞭解過其他程式語言,比如c,c++,在程式碼生成之前都需要在編輯器當中經歷 編譯 這一個步驟。在傳統的編譯語言中,程式中的一段原始碼在執行之前需要經歷三個步驟,統稱為“編譯”
1.詞法分析:
這個過程會將由字元組成的字串分解成(對於程式語言來說)有用的程式碼塊,這些程式碼塊被稱為詞法單元(token)。
2.語法分析
這個過程是將詞法單元流(陣列)轉換成一個由元素逐級巢狀所組成的代表了程式語法結構的樹。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。
3.程式碼生成
簡單來說就是有某種方法可以將 var a = 2; 的 AST 轉化為一組機器指令,用來建立一個叫作 a 的變數(包括分配記憶體等),並將一個值儲存在 a 中。
根據編譯原理繼續分析
分析的正確思路:包括變數和函式在內的所有宣告都會在任何程式碼被執行前首先被處理。
舉個例子,var a = 2;你可能會認識這是一個宣告,是一個步驟,但是實際上會js將其看成兩個宣告過程,var a 和 a = 2;第一個定義宣告是在編譯階段進行的。第二個賦值宣告會被留在原地等待執行階段。
我們回看一開始的兩個例子。
1.

var a;
a = 2;
console.log(a) //所以a為2
var a;
console.log(a);
a = 2;

其實可以這麼理解:當我們遇見一個變數的宣告時,會把他放(移動)到程式碼體的最上方,這個過程稱之為“提升”。 需要注意 :提升的只有宣告,而其他的輔助和邏輯並不會提升。
函式提升
函式提升的方式和變數提升有一些類似,函式提升會把函式的宣告“移動到”程式碼體的最上方。
舉個例子
1.函式宣告的方式

fun1();// 可以被呼叫
function fun1 (){
    console.log(a); // undefined
  var a = 3;
}

2.函式表示式的方式

fun2();// 不是 ReferenceError, 而是 TypeError!
var fun2 = function bar(){
    //...
}

執行的過程相當於

var fun2
fun2()
var fun2 = function bar(){
    //...
}