下面我們來看兩個例子
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 ()
2.函式表示式的方式
fun2();// 不是 referenceerror, 而是 typeerror!
var fun2 = function bar()
執行的過程相當於
var fun2
fun2()
fun2 = function bar()
變數提公升和函式提公升
1.變數宣告提公升 通過var 定義 宣告 的變數,在定義語句之前就可以訪問到。值 undefined console.log a undefined var a 23 console.log a 23上面 等價於 var a 預編譯,將變數宣告提公升至當前作用域的頂端,初始值為undefined ...
變數提公升和函式提公升
首先js引擎在讀取js 時會進行兩個步驟,第乙個步驟是解釋,第二個步驟是執行。所謂解釋就是會先通篇掃瞄所有的js 然後把所有宣告提公升到頂端,第二步是執行,執行就是操作一類的。例子1 console.log a 輸出結果 undefined var a 10 script 以上 輸出 undefin...
深入理解js的變數提公升和函式提公升
一 變數提公升 在es6中提出了塊級作用域,用var宣告的變數,起作用域應為對整個塊都起作用,所以會跑偏 而且使用var宣告的變數會出現 變數提公升 現象。那麼什麼是變數提公升呢?用以下 直觀感受一下 其執行順序應為 二 函式提公升 js中建立函式有兩種方式 函式宣告式和函式字面量式。只有函式宣告才...