JavaScript 的词法环境如何在嵌套块范围内维护变量声明?

How does JavaScript's lexical environment maintain variables declarations within nested block scopes?

我已经阅读了几篇关于执行上下文的更全面的文章,但现在我有点困惑,脑子里一片混乱。

为了让问题尽可能简短,避免冗长的引用,我最好尝试通过一个例子来说明我的心智模型,重点是我无法得到的细节,这样你就可以纠正我并指出错误。

这是一个例子:

var tomato = 'global tomato';

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

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

到目前为止,一切都清楚了。当 JS 引擎创建一个执行上下文(在我们的例子中是全局的)时,第一行的 var tomato 声明被放入 Variable Environment 而块范围内的 let tomato 进入 Lexical Environment.这解释了我们如何最终得到 2 个不同的西红柿。

现在,让我们添加另一个番茄,如下所示:

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 用于执行上下文,但我们可能会创建许多嵌套范围,在其中声明变量。我正在努力想象这些变量的存储位置以及整个事物如何组合在一起。

Assuming JS doesn't create a new execution context (with Lexical and Variable environments respectively) just for the blocks of code (that's only the case for function invocations and global script, right?)

这是一个错误的假设。

the specification:

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.

块语句创建一个新的词法环境。

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

I mean there is only one Lexical Environment for an execution context but we might create a numerous nested scopes declaring variables inside.

不,对于……好吧……每个词法环境都有一个词法环境。 (毕竟“范围”只是“环境”的不同术语。)

规范不强制实施者以任何特定方式实施。一种典型的方法可能是拥有环境链接列表。