提升

JavaScript 提升是指解释器在执行代码之前,似乎将函数、变量、类或导入的声明移动到其作用域的顶部的过程。

提升不是 ECMAScript 规范中规范定义的术语。规范确实将一组声明定义为可提升的声明,但这只包括 functionfunction*async function 以及 async function* 声明。提升通常也被认为是 var 声明的一个特性,尽管方式不同。用通俗的话来说,以下任何行为都可以被视为提升:

  1. 能够在声明变量之前在其作用域中使用该变量的值。(“值提升”)
  2. 能够在声明变量之前在其作用域中引用该变量而不抛出 ReferenceError,但值始终是 undefined。(“声明提升”)
  3. 变量的声明导致在声明行之前的作用域中行为发生变化。
  4. 声明的副作用在评估包含该声明的其余代码之前产生。

前面说到的四种函数声明的提升表现为第 1 种行为;var 声明的提升表现为第 2 种行为;letconstclass 声明(也称为词法声明)的提升表现为第 3 种行为;import 声明的提升表现为第 1 和第 4 种行为。

有些人更倾向于将 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 声明不是块级作用域的。

有关提升的更多信息,请参见:

参见