六角學院 JS 核心筆記 (六)【執行環境與作用域】- 提升 (Hoisting)
前言
提升 (Hoisting) 在 JavaScript 中,對於變數和函式的宣告和運行順序是很重要的觀念。但是在 ECMAScript 中其實沒有這個專有名詞,「提升」只是一個大家的共識、共同的說法,它的相關概念是寫在 Execution Contexts 中。其實我剛學 JavaScript 時,覺得它真的很奇怪,為什麼不要像其他程式一樣,好好在最前面宣告完就沒事了呢?(摔
Hoisting 是什麼?
非函式宣告
如果直接執行下面的程式碼,會出現 Uncaught ReferenceError: a is not defined
,因為沒有宣告變數 a
,產生 RHS 錯誤。
1 | console.log(a); |
但是,我如果把程式碼改成下面這樣,程式碼不會報錯,會印出 undefined
,為什麼?
1 | console.log(a); // 輸出結果:undefined |
JavaScript 的運行可以分為兩個階段:創造 / 編譯階段、執行階段。
上述的程式碼依照這兩個階段拆解,可以想像成這樣 (並不是真的移動程式碼):
1 | // 創造 / 編譯階段 |
在創造 / 編譯階段時,會先執行所有的宣告變數的動作。上述程式碼也就是會先產生 a 變數
並且賦予 undefined
這個初始值。因此當然輸出 undefined
結果。
宣告函式
函式表達式 (Function Expression)
下面這段程式碼,是函式表達式 (Function Expression),因為有 var
這個 identifier,Hoisting 的行為和非函式宣告是一樣的。它會產生 Uncaught TypeError: fn1 is not a function
錯誤。為什麼?
1 | fn1(); |
可以拆解成:
1 | // 創造 / 編譯階段 |
因為 fn1
一開始還是 undefined
,當然不能當作函式來呼叫。而呼叫的時間是在執行階段,因此產生 RHS 其他錯誤。
函式陳述式 (Function Statement)
下面這段程式碼,是函式陳述式 (Function Statement):
1 | fn1(); |
Hoisting 行為則非常不同,JS 在創造 / 編譯階段就會產生 fn1 變數
並賦予一個指向函式物件的 reference,而不是 undefined
。可以拆解成:
1 | // 創造 / 編譯階段 |
執行階段呼叫 fn1()
時,它已經有函式物件了,因此會順利輸出結果,不會報錯。
函式宣告方式參考資料:
JS 原力覺醒 Day07 - 陳述式 表達式
Hoisting 優先權
函式陳述式比函式表達式、非函式宣告有更高的優先權。
舉例一
1 | // 函式表達式 |
可以拆解成:
1 | // 創造 / 編譯階段 |
因為函式陳述式最優先,所以被提升到最前面,最後「被新的函式物件覆蓋」,反而是輸出 Hello Jenifer
。
舉例二
如果把上面的程式碼改成:
1 | // 函式陳述式 |
可以拆解成:
1 | // 創造 / 編譯階段 |
呼叫 fn1()
時,函式物件還沒有被新的內容覆蓋,因此輸出 寫在後面
。
如果想要了解基本的 Hoisting 觀念,到這裡就可以了。如果想理解背後運作的機制,可以前往:深入了解:Hoisting 和「執行環境、詞彙環境」的關係
參考資料:
六角學院:JavaScript 核心篇 - 邁向達人之路