这个“变量提升”概念如果只是表面理解的话会比较困惑。你需要深入了解语言本身的工作原理。JavaScript是ECMAScript的一种实现,是一种解释型语言,意味着你编写的所有代码都会被输入到另一个程序中,该程序反过来会“解释”代码并基于源代码的某些部分调用特定的函数。
例如,如果你写下以下代码:
function foo() {}
解释器一旦遇到您的函数声明,将调用其自己的函数
FunctionDeclarationInstantiation
来创建该函数。解释器不会将JavaScript编译成本机机器代码,而是在读取JavaScript代码的每个部分时按需执行自己的C、C++和机器代码。这并不一定意味着逐行执行,所有解释执行的意思是没有编译成机器代码。一个独立的程序
执行机器码读取您的代码,并在运行时执行那些机器码。
关于变量声明提升或任何声明与此有关的问题,解释器首先会在不执行任何实际代码的情况下先读取您的所有代码一次。它会分析代码并将其分成块,称为
词法环境。根据ECMAScript 2015语言规范:
8.1 词法环境
词法环境 是一种规范类型,用于根据 ECMAScript 代码的词法嵌套结构将 标识符 关联到特定变量和函数。词法环境由一个 Environment Record 和一个可能为 null 的对外部词法环境的引用组成。通常,词法环境与 ECMAScript 代码的某些特定语法结构(如 FunctionDeclaration、BlockStatement 或 TryStatement 的 Catch 子句)相关联,并且每次评估此类代码时都会创建一个新的词法环境。
Environment Record 记录在其关联词法环境的作用域内创建的标识符绑定。它被称为词法环境的 EnvironmentRecord。
在执行任何代码之前,解释器遍历您的代码,并为每个词法结构(如函数声明、新块等)创建一个新的词法环境。在这些词法环境中,一个环境记录记录了该环境中声明的所有变量及其值以及有关该环境的其他信息。这就是允许 JavaScript 管理变量作用域、变量查找链、this
值等的原因。
每个词法环境都与一个
代码领域相关联:
8.2 代码领域
在ECMAScript代码执行之前,必须将其与一个领域相关联。从概念上讲,领域由一组内置对象、一个ECMAScript全局环境、在该全局环境的范围内加载的所有ECMAScript代码以及其他相关状态和资源组成。
在实际执行任何代码之前,你编写的每个JavaScript/ECMAScript代码部分都与一个领域相关联。每个领域包括与领域相关联的特定代码部分使用的内在值、领域的
this
对象、领域的词法环境等等。
这意味着在执行之前会分析您代码的每个词法部分。然后
创建一个领域,其中包含有关该代码集的所有信息。源代码、执行所需的变量、已声明的变量、
this
是什么等等。对于
var
声明,创建一个领域,在此处定义函数时也是如此:
function do_something() {
console.log(bar);
var bar = 111;
console.log(bar);
}
在这里,一个
函数声明创建了一个与新领域相关联的新词法环境。当创建词法环境时,解释器会分析代码并查找所有声明。然后,在该词法环境的开头首先处理这些声明,因此是函数的“顶部”:
13.3.2 变量声明
var
语句声明作用于
正在运行的执行上下文的VariableEnvironment的变量。
当它们所在的词法环境被实例化时,Var变量被创建,并在创建时初始化为undefined。
因此,每当词法环境被实例化(创建)时,所有
var
声明都会被创建并初始化为
undefined
。这意味着它们在任何代码被执行之前在词法环境的“顶部”被处理:
var bar; //Processed and declared first
console.log(bar);
bar = 111;
console.log(bar);
然后,在分析了您的所有JavaScript代码之后,它最终被执行。由于声明首先被处理,因此它被声明(并初始化为undefined),从而使您获得undefined。
提升实际上有点误导性。提升意味着声明直接移动到当前词法环境的顶部,但实际上在执行之前会进行代码分析;没有任何东西被移动。
注意:`let`和`const`的行为方式相同,并且也会被提升,但是以下代码将不起作用:
function do_something() {
console.log(bar);
let bar = 111;
console.log(bar);
}
这将导致ReferenceError,因为尝试访问未初始化的变量。尽管
let
和
const
声明被提升,规范明确指出在初始化之前不能访问它们,不像
var
:
13.3.1 Let and Const Declarations
let
和 const
声明定义了作用域为运行执行上下文的词法环境的变量。当包含词法环境实例化时创建这些变量,但在变量的词法绑定求值之前无法以任何方式访问。
因此,在正式初始化之前,您无法访问该变量,无论是未定义还是其他任何值。这意味着您无法像使用
var
那样似乎“在声明之前访问它”。