提升
JavaScript 中的“**提升**”(Hoisting)是指解释器在代码执行之前,将函数、变量、类或 import 的*声明*“移动”到其作用域顶部的过程。
*提升*并非 ECMAScript 规范中明确定义的术语。规范确实将一组声明定义为可提升声明(*HoistableDeclaration*),但这仅包括function、function*、async function和async function*声明。提升通常也被认为是var声明的一个特性,尽管方式有所不同。通俗地说,以下任何行为都可以被认为是提升:
- 在变量声明行之前,能够在其作用域内使用该变量的值。(“值提升”)
- 在变量声明行之前,能够在其作用域内引用该变量,而不会抛出
ReferenceError,但其值始终为undefined。(“声明提升”) - 变量的声明在其声明行之前导致其作用域内的行为变化。
- 声明的副作用在其包含代码的其余部分评估之前产生。
上述四种函数声明以第一种行为类型进行提升;var声明以第二种行为类型进行提升;let、const和class声明(也统称为*词法声明*)以第三种行为类型进行提升;import声明以第一种和第四种行为类型进行提升。
有些人更喜欢将let、const和class视为不提升,因为暂时性死区严格禁止在声明之前使用变量。这种异议是合理的,因为“提升”不是一个普遍认同的术语。然而,暂时性死区可能导致其作用域内出现其他可观察到的变化,这表明存在某种形式的提升:
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。尽管如此,将词法声明描述为不提升可能更有用,因为从实用主义的角度来看,这些声明的提升并没有带来任何有意义的特性。
请注意,以下情况不属于提升:
{
var x = 1;
}
console.log(x); // 1
这里没有“在声明之前访问”;这仅仅是因为var声明不局限于块级作用域。
有关提升的更多信息,请参阅:
var/let/const提升 — 语法和类型指南function提升 — 函数指南class提升 — 类指南import提升 — JavaScript 模块