什麼是提升 (Hoisting)?

2023年2月7日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

《在 JavaScript 中用 `var`, `let`, 以及 `const` 有什麼差別?什麼時候該用哪個?》這篇文章中,曾經提到 varletconst 有「提升 (hoisting) 的差別」,而這篇文章會更詳細的回答提升 (hoisting) 究竟是什麼。

什麼是提升 (hoisting) ?

大部分人應該都曾經在寫 JavaScript 程式碼時,在宣告函式之前就使用它,如下方程式碼:

sayHello(); // Hello

function sayHello() {
  console.log("Hello");
}

但執行這樣的程式碼並不會報錯,其原因就是因為提升 (Hoisting)。

提升 (Hoisting) 並非 ECMAScript® 2015 Language Specification 中的一個正式定義,但它用來形容 JavaScript 編譯階段將變數和函式的宣告存入記憶體的概念。

這個特性會使函式和變量的宣告被提升到作用域的頂部,即使他們的實際定義位置在下面。但要注意,JavaScript 引擎並不會將程式碼實際移到頂部,而只是這個概念的形容。

變數與函式的提升

var 提升 (hoisting)

var 的提升 (hoisting) 是指在編譯階段,JavaScript 引擎會將所有的 var 變數宣告提升到該函式作用域的頂端。雖然變數宣告被提升了,但並不會賦值,如下方程式碼,提早呼叫 name 的結果會是 undefined 而非 Tom

console.log(name); // undefined
var name = "Tom";

let 提升 (hoisting)

許多人會誤以為 let 不會提升 (hoisting),因為我們如果是在宣告 let 前就使用,會出現以下錯誤。

console.log(greeting); // Uncaught ReferenceError: greeting is not defined
let greeting = "hi there";

上方的程式碼之所以會拋出錯誤,並不是因為 let 沒有 hoisting。雖然提升 (hoisting) 並沒有被定義在標準規範中的名詞解釋,但概念上,letconstvar 同樣會有提升 (hoisting) 的行為,不過其中有以下差異:

  1. var 會提升到函式作用域 (function scope),但 letconst 只會提升到區塊作用域 (block scope)
  2. var 在創建變數與定義變數範圍時,會同時將變數值自動初始化為 undefined; 但當 let 在提升變數到區塊作用域 (block scope) 範圍時,並不會初始化此變數,這個狀態可以稱之為 uninitialized,也有另一個常見的說法是,letconst 定義的變數目前存在於暫時死區 (TDZ,Temporal dead zone)

函式提升

函式宣告也有提升,與 var 提升的差異為,函式提升也會創建好函式物件,因此可以在宣告前呼叫。

foo(); // 1
function foo() {
  console.log(1);
}

但函式提升要注意的是,如果是函式表達式,提升行為會與其宣告的變數一樣,如下方程式碼,用 var 宣告的 foo 函式,在宣告前使用時,當時值會是 undefined,因此呼叫 undefined 會報錯。

foo(); // Uncaught TypeError: foo is not a function
var foo = function () {};

用 let 宣告的 foo 函式,在宣告前使用時,此時 foo 在暫時死區,因此呼叫 foo 會報錯。

foo(); // Uncaught ReferenceError: foo is not defined
let foo = function () {};

為什麼有暫時死區 (TDZ, Temporal dead zone)錯誤?

許多文章中提到,暫時死區(TDZ, Temporal dead zone) 出現好處是可以避免我們在變數在還沒有被宣告前就能拿來使用,但其實,還有另外一個最主要的設計概念。

You Don't Know JS 的作者在曾經在這門課中提出給出很好的解釋:暫時死區 (TDZ, Temporal dead zone) 錯誤其實是為了 const 所設計的。試想一下,如果 const 的提升行為與 var 相同,因此我們在宣告前訪問到 const 變量時,會拿到 undefined 的值,但我們也知道 const常數,同個作用域中值不應該變動,因此如果先拿到 undefined 後再拿到不同值的設計會不符合規範。因此,設計了暫時死區的錯誤,避免這種情況發生。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們