Hoisting 可以說是 ES6 問世之後,去面試還是會爾偶被問到的面試考題,雖然 Hoisting 離 Modern JavaScript 的技術有點距離,實作上幾乎不太會用到,但透過了解 Hoisting 的概念,可以對這門語言有更深的了解與掌握度。
在聊 Hoisting 是什麼之前,我們要先知道 Hoisting 是由什麼議題延伸出來的討論,建議可以先閱讀下文,了解 var、const、let 的差異,再來閱讀本篇文章會比較有效率:
若你已經對 JavaScript 變數宣告的作用域有所了解,就可以繼續看下去了:
究竟什麼是 Hoisting 呢?
Hoisting 中譯為「提升」,意指 JavaScript 在執行時,會將變數、函式陳述式及 class 在執行階段(execution phases)前、創造階段(creation phases)時,先行指向記憶體位置的行為。
就程式碼面而言,被提升的變數、函式陳述式及 class 的宣告會被提升至作用域 (scope)的最上方,舉例來說:
有沒有想過為什麼在 a 被宣告前,console.log(a) 依然取得到值(undefined)呢?
實際上,上方的程式碼從 JavaScript 運作開始到結束,經歷了以下階段:
而變數 a 在宣告時被提升到作用域最後上方的現象就稱為 Hoisting,但要注意的是 Hoisting 只是一個 JavaScript 執行時的現象,並沒有在 ECMA ES6 中被規範、提及。
基本上變數、函式、class 皆有 Hoisting 的狀況,但還是會依照宣告的方式不同而有所差異。
函式的 Hoisting 行為
首先讓我們先來看看有關函式的 hoisting 行為,這邊要注意的是,只有函式陳述式(function statement)有 Hoisting 的行為,函式表達式(function expression)是沒有的,舉例來說:
為什麼「catName」這個函式可以在被宣告之前就可以被呼叫呢?
因為函式陳述式在 JavaScript 會被提升,所以實際上執行的順序實際上是這樣的:
變數的 Hoisting 行為
變數的提升可以分為 var 與 const、let 兩種,第一種我們在文章的開頭介紹過了,讓我們來看看 const 與 let 的 Hoisting 行為:
與 var 的 Hoisting 行為不同,const、let 在創造階段時,並沒有像 var 一樣擁有預設值,即便他們也有 Hoisting 的行為,但呈現方式卻不同。
當使用 const、let 宣告變數時,無法在變數宣告前取用變數,連 undefined 都不會回傳,這個現象官方稱為 TDZ(Temporal Dead Zone)。
所以這裡需要注意,ES6 之後,通常我們不會在變數宣告前取用它,一來是程式的嚴禁度,二來是 ES6 以後的新發布的本版也不建議這麼設計程式。
即便會報錯,但實際上 const 、let 還是跟 var 一樣有 Hoisting 的狀況, 只差在後者帶有 undefined 的預設值,前者沒有。
Class 的 Hoisting 行為
在 ES6 時,JavaScript 引進了類別概念(class),與函式有相同的狀況,類別陳述式(class statement)具有 Hoisting 的概念,但表達式就沒有了。
但這邊要注意一件事:JavaScript 的類別並不會主動初始化,如果在 class 的宣告前取用該類別,還是會報錯:
類別 Hoisting 的狀況其實也跟 const、let 很類似,明明有 Hoisting 的行為,但卻因為程式設計嚴謹度的關係,在宣告前無法取得該類別。
避免執行錯誤的解法
基本上近期的開發者,幾乎已經棄用的 var,所以完全不用考量到 var Hoising 的狀況。
那我們該如何在撰寫程式時,避免掉 Hoisting 或是 ReferenceError 的狀況呢?
其實就是「確保函式、變數、類別被宣告後再取用」,即可避免掉 Hoisting 可能造成的錯誤了。
由於文章內還有額外提到陳述式及表達式的一些相關做法,希望之後有機會可以再來跟大家分享其中差異。
希望透過今天的文章,大家可以更加了解為什麼 var 會被棄用,及程式碼排序的重要性。
關於 Hoisting 你有什麼想要跟我分享的嗎?歡迎下方留言與我分享!
希望今天的文章有幫助到正在閱讀的你,如果你喜歡我的文章的話,可以留下你的愛心或是收藏我的文章,也或者可以點選「贊助」,你的一杯咖啡絕對是我持續寫下去的動力!或是透過拍拍手,用你小小的行動支持我的創作!
我是Vivian,我們下次見。
關於我:
一名從英文系畢業的前端工程師,喜歡閱讀、寫東西及自我成長。