提升

JavaScript 中的“**提升**”(Hoisting)是指直譯器在程式碼執行之前,將函式、變數、類或 import 的*宣告*“移動”到其作用域頂部的過程。

*提升*並非 ECMAScript 規範中明確定義的術語。規範確實將一組宣告定義為可提升宣告(*HoistableDeclaration*),但這僅包括functionfunction*async functionasync function*宣告。提升通常也被認為是var宣告的一個特性,儘管方式有所不同。通俗地說,以下任何行為都可以被認為是提升:

  1. 在變數宣告行之前,能夠在其作用域內使用該變數的值。(“值提升”)
  2. 在變數宣告行之前,能夠在其作用域內引用該變數,而不會丟擲ReferenceError,但其值始終為undefined。(“宣告提升”)
  3. 變數的宣告在其宣告行之前導致其作用域內的行為變化。
  4. 宣告的副作用在其包含程式碼的其餘部分評估之前產生。

上述四種函式宣告以第一種行為型別進行提升;var宣告以第二種行為型別進行提升;letconstclass宣告(也統稱為*詞法宣告*)以第三種行為型別進行提升;import宣告以第一種和第四種行為型別進行提升。

有些人更喜歡將letconstclass視為不提升,因為暫時性死區嚴格禁止在宣告之前使用變數。這種異議是合理的,因為“提升”不是一個普遍認同的術語。然而,暫時性死區可能導致其作用域內出現其他可觀察到的變化,這表明存在某種形式的提升:

js
const x = 1;
{
  console.log(x); // ReferenceError
  const x = 2;
}

如果const x = 2宣告根本沒有被提升(即,它只在執行時生效),那麼console.log(x)語句應該能夠從上層作用域讀取x的值。然而,因為const宣告仍然“汙染”了其定義的整個作用域,console.log(x)語句反而從const x = 2宣告中讀取x,而此時它尚未初始化,並丟擲ReferenceError。儘管如此,將詞法宣告描述為不提升可能更有用,因為從實用主義的角度來看,這些宣告的提升並沒有帶來任何有意義的特性。

請注意,以下情況不屬於提升:

js
{
  var x = 1;
}
console.log(x); // 1

這裡沒有“在宣告之前訪問”;這僅僅是因為var宣告不侷限於塊級作用域。

有關提升的更多資訊,請參閱: