iT邦幫忙

2021 iThome 鐵人賽

DAY 5
0
Modern Web

舌尖上的JS系列 第 5

D5 - 你不知道 Combo : 前菜 Hoisting

前言

cookie(); 

function cookie(){
  console.log('I love Oreo')
}

上面這段程式碼可以成功印出 I love Oreo ,尋找答案的時候會發現,哦,原來這叫 Hoisting!

但,你知道在 ECMA 中根本查不到 Hoisting 嗎
哪尼,那網路的 Hoisting 文難道都是誤人子弟?!

不不不,應該說 Hoisiting 是一個比喻說法,將背後複雜的運作原理簡化解釋,在宣告前印出結果這個現象,像是 code 被 Hoisting 提升了一樣,說法沒有錯只是不完整而已。

要從頭開始解釋 Hoisting 現象的原理,必須從 JavaScript 的編譯 開始,接著是 ScopeExecution Context 等,這些才是真正的原貌,想要深入理解 JavaScript 的話必須從這裡著手。
各位美食家們,一起來客這道 你不知道 Combo 吧!菜色應該有 2-3 道,飽足感滿滿(絕對不是為了湊篇幅 XD)

今天先上前菜,複習一下 JavaScript 中 那些 Hoisting 現象 及 錯誤訊息差異

那些稱為 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,再使用 varletconst 分別宣告 a,b,c 並個別賦值,最後 d 不進行宣告直接賦值。

結果顯示:

  • 使用 var 宣告的 a 印出 undefined
  • letconst 宣告的 b 、 c 拋出錯誤訊息 can't access before initialization
  • 沒有宣告的 d 拋出錯誤訊息 d is not defined

有趣了,出現三種不同結果,看來宣告是關鍵 (背後一道柯南閃電)
這些現象稱為 hoisting,規則是

  1. 變數的宣告會被 hoisting,但須區分是 var 還是 let const 宣告
  2. 變數的 初始化 Initialization 不會被 hoisting

先來個名詞解釋 初始化 Initialization 是什麼?

將一個什麼都還不是的變數賦予值,像是 b=5 這個動作就是所謂的初始化 Initilization,簡單來說,就是對變數第一次賦值,從無到有的過程稱為「初始化」。

再更白話的解釋這兩點,可以想像 JavaScript 在正式執行 code 以前,會先全部掃描一次,當看到使用 varletconst 關鍵字宣告變數時,會先將這些變數名稱記起來,但並不會記下變數的值,所以 console.log(a) 時,因為掃描過一次知道有 a 這個變數,但還不知道它的值,所以印出 undefined。

而 ES6 之後因為要求先宣告重要性,所以經由letconst 宣告的變數會顯示錯誤訊息 Cannot access 'b' before initialization 無法在變數初始化以前取得值,提醒「我知道你想要印出變數,但他們還沒有被記錄到值,順序換一下吧!」

而最後的 d,因為根本沒被宣告,一開始掃描時不會記下這個變數,所以印出錯誤訊息 - 變數 not defined 「嘿,請問你 d 是哪位?」

Function

接下來講講 function 的 Hoisting 行為,首先要先能區分 Function DeclarationFunction 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

好的,那接下來來看看 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 宣告)

執行後出現三個不同結果:

  • oreo 成功印出字串 'Oreo is my forever love'
  • var 宣告的 twix 跑出錯誤訊息 TypeError: twix is not a function
  • let 宣告的 ritz 跑出錯誤訊息 ReferenceError: Cannot access 'ritz' before initialization

總結 function 的 Hoisting 規則:

  1. 只有以 Function Declaration 創建的 function 才會被 Hoisting
  2. Function Expression

只要記得,只有 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

Reference:

大量參考 [你所不知道的JS] by Kyle Simpson
Twitter 截圖來源JavaScript: 变量提升和函数提升
Huli大師的超專業解釋文 我知道你懂 hoisting,可是你了解到多深?
Two words about "hoisting"
MDN
W3School


上一篇
D4 - 加鹽不加價 嚴格模式開啟
下一篇
D6 - 你不知道 Combo : 主菜 Scope 字彙環境
系列文
舌尖上的JS30

2 則留言

0
南國ㄟ安迪
iT邦新手 5 級 ‧ 2021-09-20 21:43:09

絕對不是為了湊篇幅
絕對不是肚子餓
絕對不是覺得大叔超過25

Hooo iT邦新手 5 級 ‧ 2021-09-21 19:25:01 檢舉

絕對不是

0
Rex
iT邦新手 5 級 ‧ 2021-09-21 20:00:59

新生兒跪著學 hoisting

Hooo iT邦新手 5 級 ‧ 2021-09-28 10:29:49 檢舉

免禮

我要留言

立即登入留言