提升

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声明不局限于块级作用域。

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