JavaScript 的词法环境如何在嵌套块作用域中维护变量声明?

6

我已经阅读了几篇关于执行上下文的综合文章,但现在我有点困惑和混乱。

为了尽可能简短地提出问题,避免长篇引用,我最好通过一个例子来说明我的心理模型,并重点关注我无法理解的细节,以便您可以纠正我并指出错误。

以下是一个例子:

var tomato = 'global tomato';

{
  let tomato = 'block tomato';
  console.log(tomato); // 'block tomato'
}

console.log(tomato); // 'global tomato'

到目前为止,所有的都很清楚。当JS引擎创建执行上下文(在我们这个例子中是全局上下文)时,来自第一行的var tomato声明被放置在一个变量环境中,而块级作用域中的let tomato则进入了词法环境。这就解释了为什么我们最终得到了两个不同的tomatoes。
现在,让我们再添加另一个tomato,就像这样:
var tomato = 'global tomato';

{
  let tomato = 'block tomato';

  {
    console.log(tomato); // ReferenceError: Cannot access 'tomato' before initialization
    let tomato = 'nested block tomato';
  }

  console.log(tomato); // won't reach here
}

console.log(tomato); // won't reach here

ReferenceError 不足为奇。事实上,我们试图在变量初始化之前访问它,这被称为暂时性死区。这很好地说明了JS已经在最嵌套的块中创建了另一个变量tomato。此外,JS已经意识到当前引用的tomato未被初始化。否则,它将从等于 'block tomato' 的外部作用域中获取tomato而不会抛出任何错误。因此,让我们修复错误并交换这两行代码,像这样:

var tomato = 'global tomato';

{
  let tomato = 'block tomato';

  {
    let tomato = 'nested block tomato';
    console.log(tomato); // 'nested block tomato'
  }

  console.log(tomato); // 'block tomato' - still 'block tomato'. Nothing has been overwritten.
}

console.log(tomato); // 'global tomato'

我想知道的是JavaScript是如何管理最嵌套的代码块的。因为执行到以下这行代码时:

let tomato = 'nested block tomato';

执行上下文执行时,变量的Lexical Environment已经包含在外部范围初始化的变量tomato并赋值为'block tomato'。假设JS不会为代码块创建新的执行上下文(具有各自的词法和变量环境),除了函数调用和全局脚本之外(是这样吧?),显然它不会覆盖现有的Lexical Environment中同名但来自嵌套块作用域的变量。就像最后一段代码所示,创建了一个全新、独立的变量来存储值'nested block tomato'
那么问题来了,这个变量究竟存储在哪里呢?我的意思是,一个执行上下文只有一个 Lexical Environment ,但我们可能会在其中创建 numerous 个声明变量的嵌套作用域。我很难想象这些变量将被存储在哪里,以及整个东西如何拼合在一起。

解释是,JavaScript 首先在其环境中查找变量,然后它会向上查找作用域,直到全局范围,如果全局范围中不存在,则会出现未定义错误或类似的错误。 - The Bomb Squad
1
这些变量都被称为“tomato”是无关紧要的。它们也可以被称为“x”,“y”和“z”,并且您将获得相同的行为,只是更容易看到它们之间的不相关性。每次查找变量时,查找从当前环境开始,如果在那里找不到,则向上遍历链接的环境。两个环境之间没有冲突或混淆,因为它们是不同的变量。 - VLAZ
Chrome开发者工具调试器可以显示作用域,也称为词法环境。 - marzelin
2个回答

5
假设JS不为代码块创建新的执行上下文(具有词法和变量环境),这是只有在函数调用和全局脚本的情况下才会发生的,对吗?
这是一个错误的假设。
请参阅规范
词法环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构将标识符与特定变量和函数关联。词法环境由环境记录和对外部词法环境的可能为空的引用组成。通常,词法环境与ECMAScript代码的某些特定语法结构相关联,例如FunctionDeclaration、BlockStatement或TryStatement的Catch子句,并且每次评估此类代码时都会创建一个新的词法环境。
块语句会创建一个新的词法环境。

4

ECMAScript中,词法作用域可以嵌套。

我的意思是,每个执行上下文只有一个词法环境,但我们可能会在其中创建许多嵌套的作用域,并在其中声明变量。

不,对于每个词法环境都有一个词法环境。毕竟,“作用域”只是“环境”的不同术语。

规范并没有强制实现者以任何特定方式实现这一点。一种典型的方式可能是具有环境链接列表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接