Day 10:程式也懂電梯向上? —— Hoisting
Last updated
Last updated
有沒有注意過 JavaScript 裡一個神奇的現象?
比如以下程式:
執行結果:
(Source: 白爛貓貼圖)
因為使用了根本不存在的變數 x
,執行時拋出錯誤,非常合理。
為了彌補,我們趕快增加 x
的變數宣告:
你程式老師在你後面,他非常火。
(Source: Youtube)
變數宣告當然是要在使用之前,補在後面有什麼用,一樣會先執行到拋錯的那一行。
執行結果:
(Source: 白爛貓貼圖)
Why?為什麼宣告在使用之後不會拋錯?而且印了個莫名的答案。
我知道了。
喂,冷靜。
程式不會變魔術,一定有理可循。
其實背後的原因,就是今天文章要介紹的 Hoisting。
Hoisting 這個術語在 MDN 裡翻譯為「提升」,但我覺得這個名詞太抽象,概念上不易理解。
我個人偏好翻成「宣告置頂」。
就像生活中網路論壇常見的置頂文章,當一些文章被設定為置頂文,無論你進入討論版的下一步想做什麼,這些置頂文都會優先被看到。
Hoisting 的效果非常類似這樣的概念。
以下是 W3Schools 裡的介紹:
Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope.
Hoisting 是 JavaScript 的預設行為,把所有宣告效果提到當前 Scope 的頂端。
也就是說,在正式執行程式之前,JavaScript 會先偷跑一個動作——把程式碼中宣告的部分提前到所屬 Scope 的頂端。
var
關鍵字)例如文章開頭舉的例子:
運作上等同於:
這就是為什麼 JavaScript 變數可以在宣告之前就使用,而不會拋錯。
但我的程式碼明明是 var x = "OneJar";
,宣告同時就給予初始值,為何印出來的結果是 undefined
而非 "OneJar"
?
再引用 W3Schools 裡的原文:
JavaScript Initializations are Not Hoisted.
也就是說,只有宣告的部分會被提升。
這個觀念不那麼直觀,因為不是以一整行的程式碼來看,而是單獨抽出了程式碼中「宣告」的部分。
以 var x = "OneJar";
這行程式來說,裡面包含 2 個動作: 1. var x
:宣告一個變數名叫 x
。 2. x = "OneJar"
:將賦值給變數 x
(撰寫時將宣告和賦值寫在同一行,這個動作也稱為「初始化」)。
Hoisting 的效果只涵蓋動作 1,不涵蓋動作 2。
所以這段程式:
經過 Hoisting 後等同於:
而非:
function
關鍵字)以下這種寫法一定不陌生:
執行結果:
是否曾經覺得奇怪,為什麼函數 sayHi()
宣告定義在後面,卻可以提前呼叫?
這也是 Hoisting 效果。
使用 function
關鍵字去宣告函數,整個函數定義都會被提到 Scope 最前面。
定義函數除了直接使用 function
做宣告和定義,也允許用「var
變數宣告 + function
函數定義」的寫法。
但需要注意的是,就像前面提到:Hoisting 效果只有「宣告」的部分,不包含「初始化」。
例如以下例子:
執行結果:
被提升的只有「var
變數宣告」的部分,「function
函數定義」的部分仍在原本的位置。
運作上的效果就像以下程式碼:
let
或 const
宣告的變數不具備 Hoisting 效果W3Schools:
Variables and constants declared with let or const are not hoisted!
Day8 文章介紹到 ES6 導入新的變數宣告關鍵字:let
和 const
。
需要注意到,這兩個關鍵字所宣告的變數不會有 Hoisting 效果。
執行結果:
let
和 const
其實也有 Hoisting感謝邦友 Caesar 提供一篇文章——我知道你懂 hoisting,可是你了解到多深?,才了解其實 let
和 const
有 hoisting,只是行為不一樣。
例如以下範例:
如果沒有 hoisting,理論上應該會根據 Scope Chain 找到外面的 x
,印出 "OneJar"
。
但實際上的執行結果:
原因簡單來說,節錄文章的一句話:
let 與 const 也有 hoisting 但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤。
文章作者花了很多篇幅講解 hoisting 背後的運作原理,方知小小的 hoisting 觀念要深入,細節也是無窮無盡。
Hoisting 效果包含: 1. 使用 var
的變數宣告。 2. 使用 function
宣告的函數與其定義。
Hoisting 效果不包含: 1. 初始化的部分 (Initializations),例如變數初始值或使用 var
宣告的函數定義。 2. 使用 let 或 const 的變數宣告。 (實際上有,但行為和 var
不一樣)
(Source: 網路)