如果 JavaScript 是一种解释型语言,提升是如何工作的?

How does hoisting work if JavaScript is an interpreted language?

我对解释器的理解是它逐行执行程序,我们可以看到即时结果,不像编译型语言转换代码然后执行它。

我的问题是,在 Javascript 中,解释器如何知道程序中某处声明了一个变量并将其记录为 undefined

考虑下面的程序:

function do_something() {
  console.log(bar); // undefined (but in my understanding about an interpreter, it should be throwing error like variable not declared)
  var bar = 111;
  console.log(bar); // 111
}

隐式理解为:

function do_something() {
  var bar;
  console.log(bar); // undefined
  bar = 111;
  console.log(bar); // 111
}

这是如何工作的?

"Interpreted" 并不代表您认为的那样。

实际上,"interpreted" 这里的意思更像是 "compiled on demand",它不是逐行编译(如您所想),而是以可执行代码为单位进行编译。这些单元首先被读入内存,然后被执行。

正是在这些阶段,执行上下文的范围变得已知,声明被提升,标识符被解析。

所有这些实现的细节都没有标准化,每个供应商都可以随意实现它们。

'var 提升'这个概念如果你从表面上看是一个相当混乱的概念。您必须深入研究语言本身的工作原理。 JavaScript,它是 ECMAScript 的一种实现,是一种解释型语言,这意味着您编写的所有代码都被输入到另一个程序中,该程序依次 解释 代码,调用某些函数基于您的部分源代码。

例如,如果你写:

function foo() {}

解释器一旦满足您的函数声明,就会调用一个名为 FunctionDeclarationInstantiation 的函数来创建该函数。解释器不是将 JavaScript 编译成本机机器代码,而是在读取 JavaScript 代码的每个部分时执行 C、C++ 和它自己的 'on demand' 机器代码。它不一定意味着逐行,所有解释意味着没有编译成机器代码。一个单独的程序 执行机器代码 读取您的代码并即时执行该机器代码。

这与 var 声明提升或与此相关的任何声明有什么关系,是解释器首先读取所有代码一次,而不执行任何实际代码。它分析代码并将其分成块,称为词法环境Per the ECMAScript 2015 Language Specification:

8.1 Lexical Environments

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment. Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a BlockStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

An Environment Record records the identifier bindings that are created within the scope of its associated Lexical Environment. It is referred to as the Lexical Environment’s EnvironmentRecord

在执行任何代码之前,解释器会检查您的代码并为每个词法结构(例如函数声明、新块等)创建一个新的词法环境。在这些词法环境中,环境记录 记录了在该环境中声明的所有变量、它们的值以及有关该环境的其他信息。这就是允许 JavaScript 管理变量范围、变量查找链、this 值等的原因。

每个词法环境都与 code realm:

8.2 Code Realms

Before it is evaluated, all ECMAScript code must be associated with a Realm. Conceptually, a realm consists of a set of intrinsic objects, an ECMAScript global environment, all of the ECMAScript code that is loaded within the scope of that global environment, and other associated state and resources.

您编写的 JavaScript/ECMAScript 代码的每个部分在实际执行任何代码之前都与一个领域相关联。每个领域都包含与领域关联的特定代码部分使用的内在值、领域的 this 对象、领域的词法环境等。

这意味着代码的每个词法部分都会在执行前进行分析。然后 a realm is created 包含了那组代码的所有信息。源,执行它需要什么变量,已经声明了哪些变量,this 是什么,等等。在 var 声明的情况下,当你定义一个像你这样的函数时,就会创建一个领域在这里做了:

function do_something() {
  console.log(bar); // undefined
  var bar = 111;
  console.log(bar); // 111
}

在这里,FunctionDeclaration 创建了一个新的词法环境,与一个新的领域相关联。创建词法环境时,解释器会分析代码并找到所有声明。然后首先在该词法环境的最开始处理这些声明,因此函数的 'top':

13.3.2 Variable Statement

A var statement declares variables that are scoped to the running execution context’s VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized to undefined when created.

因此,每当实例化(创建)词法环境时,都会创建所有 var 声明,并初始化为 undefined。这意味着它们在执行任何代码之前被处理,在词法环境的 'top':

var bar; //Processed and declared first
console.log(bar);
bar = 111;
console.log(bar);

然后,所有你的JavaScript代码分析完后,最后执行。因为首先处理声明,所以它被声明(并初始化为 undefined)给你 undefined.

提升机确实有点用词不当。提升意味着将声明直接移动到当前词法环境的顶部,而是在执行之前分析代码;什么都没有移动。


注意:letconst 的作用相同,并且 也被提升了 但这行不通:

function do_something() {
  console.log(bar); //ReferenceError
  let bar = 111;
  console.log(bar);
}

这会给你一个 ReferenceError 试图访问一个未初始化的变量。即使 letconst 声明被提升,规范 明确指出 在它们被初始化之前你不能访问它们,不像 var:

13.3.1 Let and Const Declarations

let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

因此,在变量正式初始化之前,您不能访问该变量,无论是未定义的还是任何其他值。这意味着您似乎无法像使用 var.

那样“在声明之前访问它”