什麼是提升 (Hoisting)?
2023年2月7日
在《在 JavaScript 中用 `var`, `let`, 以及 `const` 有什麼差別?什麼時候該用哪個?》這篇文章中,曾經提到 var
、let
與 const
有「提升 (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) 並沒有被定義在標準規範中的名詞解釋,但概念上,let
、const
跟 var
同樣會有提升 (hoisting) 的行為,不過其中有以下差異:
var
會提升到函式作用域 (function scope),但let
和const
只會提升到區塊作用域 (block scope)var
在創建變數與定義變數範圍時,會同時將變數值自動初始化為undefined
; 但當let
在提升變數到區塊作用域 (block scope) 範圍時,並不會初始化此變數,這個狀態可以稱之為 uninitialized,也有另一個常見的說法是,let
和const
定義的變數目前存在於暫時死區 (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
後再拿到不同值的設計會不符合規範。因此,設計了暫時死區的錯誤,避免這種情況發生。