cookie();
function cookie(){
console.log('I love Oreo')
}
上面這段程式碼可以成功印出 I love Oreo ,尋找答案的時候會發現,哦,原來這叫 Hoisting!
但,你知道在 ECMA 中根本查不到 Hoisting 嗎
哪尼,那網路的 Hoisting 文難道都是誤人子弟?!
不不不,應該說 Hoisiting 是一個比喻說法,將背後複雜的運作原理簡化解釋,在宣告前印出結果這個現象,像是 code 被 Hoisting 提升了一樣,說法沒有錯只是不完整而已。
要從頭開始解釋 Hoisting 現象的原理,必須從 JavaScript 的編譯 開始,接著是 Scope 及 Execution Context 等,這些才是真正的原貌,想要深入理解 JavaScript 的話必須從這裡著手。
各位美食家們,一起來客這道 你不知道 Combo 吧!菜色應該有 2-3 道,飽足感滿滿(絕對不是為了湊篇幅 XD)
今天先上前菜,複習一下 JavaScript 中 那些 Hoisting 現象 及 錯誤訊息差異
W3School 是這麼解釋 hoisting 的
Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function)
Hoisting 為 JavaScript 的預設行為,將所有宣告提升到所在範圍的頂端( 這裡的範圍可能是 全域的頂端 或是 function 的頂端)。
console.log(a); // undefined
console.log(b); // ReferenceError: Cannot access 'b' before initialization
console.log(c); // ReferenceError: Cannot access 'c' before initialization
console.log(d); // ReferenceError: d is not defined
var a = 4;
let b = 5;
const c = 6;
d = 7;
首先,先來看看上面這段程式碼,在開始時要求印出變數 a、b、c、d,再使用 var
、let
、const
分別宣告 a,b,c 並個別賦值,最後 d 不進行宣告直接賦值。
結果顯示:
var
宣告的 a 印出 undefined
let
和 const
宣告的 b 、 c 拋出錯誤訊息 can't access before initialization
有趣了,出現三種不同結果,看來宣告是關鍵 (背後一道柯南閃電)
這些現象稱為 hoisting,規則是
var
還是 let
const
宣告將一個什麼都還不是的變數賦予值,像是 b=5
這個動作就是所謂的初始化 Initilization,簡單來說,就是對變數第一次賦值,從無到有的過程稱為「初始化」。
再更白話的解釋這兩點,可以想像 JavaScript 在正式執行 code 以前,會先全部掃描一次,當看到使用 var
、let
、const
關鍵字宣告變數時,會先將這些變數名稱記起來,但並不會記下變數的值,所以 console.log(a)
時,因為掃描過一次知道有 a 這個變數,但還不知道它的值,所以印出 undefined。
而 ES6 之後因為要求先宣告重要性,所以經由let
和 const
宣告的變數會顯示錯誤訊息 Cannot access 'b' before initialization 無法在變數初始化以前取得值,提醒「我知道你想要印出變數,但他們還沒有被記錄到值,順序換一下吧!」
而最後的 d,因為根本沒被宣告,一開始掃描時不會記下這個變數,所以印出錯誤訊息 - 變數 not defined 「嘿,請問你 d 是哪位?」
接下來講講 function 的 Hoisting 行為,首先要先能區分 Function Declaration 和 Function Expression
(英文表示最精準,仿間各種中文翻譯:陳述式 表達式 宣告式 運算式???到底叫啥我頭都痛了)
直接以 function 開頭並為它命名,基本上都稱為 Function Declaration 或稱為 Function Statement
function oreo(){
return 'I love Oreo 3000'
}
而 Function Expression 會將 function 賦予到一個變數內,通常以匿名函式 或 箭頭函式形式出現
let oreo = function (){
return 'I love Oreo 3000'
}
//或箭頭函式型態
let oreo = () => 'I love Oreo 3000'
好的,那接下來來看看 function 的 Hoisting 效果
oreo(); // 'Oreo is my forever love'
twix(); // TypeError: twix is not a function
ritz(); // ReferenceError: Cannot access 'ritz' before initialization
function oreo() {
console.log('Oreo is my forever love')
}
var twix = () => {
console.log('Twix is sooo good!')
}
let ritz = function () {
return 'Ritz is much better'
}
以上定義了三個 function,分別是 Function Declaration 下的 oreo 、Function Expression 下的 twix ( var 宣告)及 ritz ( let 宣告)
執行後出現三個不同結果:
總結 function 的 Hoisting 規則:
只要記得,只有 Function Declaration 有 Hoisting 效果,依照當初的設計考量,這是為了提升 function 互相呼叫的靈活度
在此篇 Two words about “hoisting” 的文章作者,在 Twitter tag 了 JavaScript 之父 Brendan Eich 關於 Hoisting 的設計由來, Brendan Eich 也回覆了 Function Declaration 的 Hoisting 是為了 更方便的遞迴效果 及 解決程式碼的順序問題
Brendan Eich Twitter 回覆截圖
這個設計在 JavaScript 內是非常方便的,它避免了在一團亂糟糟的 code 下尋找呼叫的入口,可以在最開始時先呼叫 function,程式碼看起來更乾淨,也不管 function 寫在哪,呼叫就取得到。
而至於為何 Function Expression 無法呢?
他的型態是將 function 存入變數,同先前提到的變數 Hoisting 行為,只有名稱會被記住,值並不會被 Hoisting,
所以對一個 var
宣告的變數使用 ()
小括號呼叫,就是對 undefined 呼叫,是根本上的語法錯誤,所以顯示 TypeError
而 let
宣告的 ritz 因為尚未取得值,錯誤訊息一樣跑出 Cannot access 'rits' before initialization.
以上就是 Hoisting 在 JavaScript 中的行為,明天來看看引擎端會怎麼解讀這些 code
大量參考 [你所不知道的JS] by Kyle Simpson
Twitter 截圖來源JavaScript: 变量提升和函数提升
Huli大師的超專業解釋文 我知道你懂 hoisting,可是你了解到多深?
Two words about "hoisting"
MDN
W3School