【问题标题】:How does hoisting work if JavaScript is an interpreted language?如果 JavaScript 是一种解释性语言,提升如何工作?
【发布时间】:2018-01-19 01:50:17
【问题描述】:

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

我的问题是,在 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
}

这是如何工作的?

【问题讨论】:

  • 你需要一本关于 javascript 的书并阅读它。这称为提升。
  • 你对“interpreter”的理解是错误的。
  • 这是一个有效的问题。我想知道同样的大声笑。我想,如果代码首先被解释(逐行)以定义变量,然后再次被解释以实际执行代码怎么办?想想哈哈哈哈

标签: javascript variables hoisting


【解决方案1】:

'var 提升'这个概念,如果你从表面上考虑的话,是相当令人困惑的。你必须深入研究语言本身是如何工作的。 JavaScript 是 ECMAScript 的一种实现,是一种解释型语言,这意味着您编写的所有代码都被输入到另一个程序中,而该程序又解释代码,并根据您的部分源代码调用某些函数.

例如,如果你写:

function foo() {}

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

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

8.1 词法环境

词法环境 是一种规范类型,用于根据 ECMAScript 代码的词法嵌套结构定义 标识符 与特定变量和函数的关联。一个词法环境由一个Environment Record 和一个可能为空的对外部 词法环境的引用组成。通常,词法环境与 ECMAScript 代码的某些特定句法结构相关联,例如 FunctionDeclarationBlockStatementCatch 子句em>TryStatement 并且每次评估此类代码时都会创建一个新的词法环境。

Environment Record 记录在其关联词法环境范围内创建的标识符绑定。它被称为词法环境的EnvironmentRecord

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

每个词法环境都与一个code realm相关联:

8.2 代码领域

在评估之前,所有 ECMAScript 代码都必须与 Realm 相关联。从概念上讲,一个领域由一组内在对象、一个 ECMAScript 全局环境、在该全局环境范围内加载的所有 ECMAScript 代码以及其他相关的状态和资源组成。

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

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

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

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

13.3.2 变量语句

var 语句声明范围为the running execution context’s VariableEnvironment 的变量。 Var 变量在其包含 Lexical Environment 的实例化时创建,并在创建时初始化为 undefined。

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

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

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

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


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

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

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

13.3.1 Let 和 Const 声明

letconst 声明定义范围为 the running execution context’s LexicalEnvironment 的变量。变量在实例化包含 Lexical Environment 时创建,但在评估变量的 LexicalBinding 之前,不得以任何方式访问。

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

【讨论】:

  • 你会说作用域和词法环境是一回事吗?
【解决方案2】:

“解释”并不意味着你认为它是什么。

实际上,这里的“解释”更像是“按需编译”,而不是逐行编译(如您所想),它是以可执行代码为单位编译的。这些单元首先被读入内存,然后被执行。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-19
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多