ECMAScript:我们在哪里可以找到关于 let/const 变量可访问性的规范

ECMAScript: Where can we find specification about accessibility of let/const variables

ECMAScript specification 中,我们在哪里可以找到关于为什么 letconstLexical Environments 之外无法访问的明确说明使用 BlockStatements 创建(相对于使用 var 声明的变量)?

如果 BlockStatements 现在创建 new lexical environments,那么 letconst 声明不应创建可在词法环境之外访问的变量,但是 var变量应该。我试图了解最新的 ECMAScript 规范中具体在哪里指定了该行为。

来自 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.

来自 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.

正如所见,两个变量声明都在实例化包含 词法环境 时创建变量。其中,在 BlockStatement 的情况下是编译器进入块的时间。

来自 8.3 Execution Contexts:

LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments

正如您在 var 的描述中看到的,它的范围是 运行 执行上下文的 VariableEnvironment。有一个顶层 VariableEnvironment 然后创建一个新的 when you enter a function and then in this part about Execution Contexts,它说的是:

The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value.

因此,在一个函数的开头,LexicalEnvironment 和 VariableEnvironment 是同一个。

然后,在 13.2.13 Runtime Semantics: Evaluation Block: { } 中,您可以看到当您进入该区块时会创建一个新的 LexicalEnvironment,而当您离开该区块时,会恢复之前的 LexicalEnvironment。但是,当您进入或离开一个块时,没有提到新的 VariableEnvironment(因为它在函数中保持不变)。

因此,由于 letconst 的作用域是声明它们的 LexicalEnvironment,并且是块的局部区域,因此无法在块外访问。

但是,var 的作用域为 VariableEnvironment,它仅被创建并作用于整个函数,而不是一个块。

letconst 变量无法在其 LexicalEnvironment 之外访问,因为一旦执行上下文离开它们的块(一旦您离开块,它们的 LexicalEnvironment 基本上从堆栈中弹出,不再在解释器查找变量的范围搜索链中)。


当规范添加这个时:

The [let and const] 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 那样被提升到作用域的顶部,因此只有在定义之后才能使用。这是通过在 LexicalEnvironment 中不初始化 letconst 变量来实现的,直到它的语句运行并且查找变量的 GetBindingValue() 操作将看到它还没有初始化并将抛出 ReferenceErrorvar 变量立即初始化为 undefined,因此它们不会导致此 ReferenceError.

您可以在这段代码中看到它是如何工作的:

let x = 3;

function test() {
    x = 1;
    let x = 2;
    console.log("hello");
}
test();

let x = 3 行,一个变量 x 在外部 LexicalEnvironment 中被初始化。

然后,当您调用 test() 时,在该函数的开头,将创建一个新的 LexicalEnvironment,此块中 x 的新声明将放入新的 LexicalEnvironment,但尚未初始化。

然后,您将进入 x = 1 语句。解释器查找x,在当前LexicalEnvironment中找到它,但是它是未初始化的,所以它抛出一个ReferenceError.


根据您评论中的问题:

I've been turning the spec upside down, but have a hard time spotting that VariableEnvironments are only created for functions. Could you perhaps add an answer showing what steps in the spec you follow to reach said conclusion?

你必须遍历规范中创建 VariableEnvironment 的所有地方,你会发现唯一发生这种情况的地方是函数执行的开始和顶层。

例如,在这些地方有一个:PrepareForOrdinaryCall。还有其他一些。

但是,它从来没有描述过块开始时发生的这种情况,只有函数的开始。

这些规范的编写方式,它们描述了事情何时发生,而不是何时没有发生(这在逻辑上是合理的),但这意味着要证明某事没有发生,你必须失败找到任何地方说它确实发生了。