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

fek*_*oid 6 javascript callstack lexical lexical-scope hoisting

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

为了使问题尽可能简短,避免长时间引用,我最好尝试通过一个专注于我无法获得的细节的示例来说明我的心理模型,以便您可以纠正我并指出错误。

下面是一个例子:

var tomato = 'global tomato';

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

console.log(tomato); // 'global tomato'
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都清楚了。当 JS 引擎创建一个执行上下文(在我们的例子中是全局的)var tomato时,第一行的声明被放置到一个块范围内的Variable Environmentwhile 中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
Run Code Online (Sandbox Code Playgroud)

ReferenceError不足为奇。事实上,我们试图在变量被初始化之前访问它,这被称为临时死区。这很好地表明 JS 已经在嵌套最多的块中创建了另一个变量tomatotomato在我们引用它的那一刻,JS 也已经意识到它没有被初始化。否则,它会tomato从外部作用域中抓取,这等于'block 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'
Run Code Online (Sandbox Code Playgroud)

我想知道 JavaScript 如何管理这个嵌套最多的块。因为到执行到达该行时:

let tomato = 'nested block tomato';

Lexical Environment执行上下文的已含有变量tomato这是在用的值的范围外初始化'block tomato'。假设 JS 不只为代码块创建一个新的执行上下文(分别使用词法和变量环境)(这只是函数调用和全局脚本的情况,对吧?)并且显然,它不会覆盖现有的变量Lexical Environment具有相同名称但来自嵌套块作用域的那些。正如最后一段代码所示,创建了一个全新的独立变量来保存 value 'nested block tomato'

那么问题来了,这个变量究竟存储在哪里呢?我的意思是只有一个Lexical Environment用于执行上下文,但我们可能会创建许多嵌套的作用域来声明变量。我正在努力想象这些变量的存储位置以及整个事情如何组合在一起。

Que*_*tin 5

假设 JS 不只为代码块创建一个新的执行上下文(分别使用词法和变量环境)(这只是函数调用和全局脚本的情况,对吧?)

这是一个错误的假设。

规格

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

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