Mag*_*nus 1 javascript ecmascript-6 ecmascript-2017
在ECMAScript 规范中,我们在哪里可以找到明确的规范,说明为什么let和const不能在使用BlockStatements创建的词法环境之外访问(而不是使用 声明的变量)?var
如果BlockStatements现在创建新的词法环境,则let和const声明不应创建在该词法环境之外可访问的变量,但var变量应该创建。我试图了解最新的 ECMAScript 规范中到底在哪里指定了该行为。
let 和 const 声明定义了作用域为运行执行上下文的 LexicalEnvironment 的变量。这些变量是在实例化其包含的词法环境时创建的,但在评估变量的 LexicalBinding 之前不能以任何方式访问。
var 语句声明的变量的作用域为正在运行的执行上下文的 VariableEnvironment。Var 变量在实例化其包含的词法环境时创建,并在创建时初始化为未定义。
正如所见,两个变量声明在实例化它们所包含的词法环境时都会创建变量。对于 BlockStatement 来说,就是编译器进入块的时间。
来自8.3 执行上下文:
执行上下文的 LexicalEnvironment 和 VariableEnvironment 组件始终是词法环境
正如您在 的描述中看到的var,它的作用域是正在运行的执行上下文的VariableEnvironment。有一个顶层VariableEnvironment,然后当您输入一个函数时会创建一个新的顶层,然后在关于执行上下文的这一部分中,它说:
执行上下文的 LexicalEnvironment 和 VariableEnvironment 组件始终是词法环境。创建执行上下文时,其 LexicalEnvironment 和 VariableEnvironment 组件最初具有相同的值。
因此,在函数开始时,词法环境和变量环境是相同的。
然后,在13.2.13 运行时语义:评估块:{ }中,您可以看到当LexicalEnvironment您进入该块时会创建一个新块,而当您离开该块时会恢复前一个块。但是,当您进入或离开块时,不会提及新的VariableEnvironment(因为它在函数内保持不变)。
因此,由于let和 的const作用域是在其中声明它们的 LexicalEnvironment 并且是块本地的,因此在块之外无法访问它。
但是,var它的作用域VariableEnvironment仅是创建的,并且作用域是整个函数,而不是块。
let并且const变量不能在它们的 LexicalEnvironment 之外访问,因为一旦执行上下文离开它们的块,它们的定义就不在作用域层次结构中(一旦离开块,它们的 LexicalEnvironment 本质上就会从堆栈中弹出,不再在作用域层次结构中)解释器查找变量的范围搜索链)。
当规范添加此内容时:
[
let和const] 变量是在实例化其包含的词法环境时创建的,但在评估变量的 LexicalBinding 之前不能以任何方式访问。
这意味着即使在它们自己的词法环境中,您也无法访问它们,直到评估它们的定义为止。用外行人的话来说,这意味着它们不会像现在一样提升到其范围的顶部var,因此在定义之后才能使用。这是通过在语句运行之前不初始化 a letorconst变量来实现的,查找变量的操作将发现它尚未初始化并将抛出. 变量会立即初始化,因此不会导致此问题。LexicalEnvironmentGetBindingValue()ReferenceErrorvarundefinedReferenceError
您可以在这段代码中看到它是如何工作的:
let x = 3;
function test() {
x = 1;
let x = 2;
console.log("hello");
}
test();
Run Code Online (Sandbox Code Playgroud)
在该let x = 3行,变量x在外部词法环境中初始化。
然后,当您test()在该函数的开头调用 时,会创建一个新的 LexicalEnvironment ,该块中的新声明 forx会被放入该 new 中LexicalEnvironment,但尚未初始化。
然后,你就可以看到x = 1声明了。解释器查找x,在 current 中找到它LexicalEnvironment,但它是未初始化的,所以它抛出一个ReferenceError.
根据您评论中的问题:
我一直在颠倒规范,但很难发现变量环境只是为函数创建的。您能否添加一个答案,显示您在规范中遵循哪些步骤来得出上述结论?
您只需浏览规范中VariableEnvironment创建 a 的所有位置,您就会发现这种情况发生的唯一位置是函数执行的开头和顶层。
例如,以下是这些地方的一个:PrepareForOrdinaryCall。还有其他一些。
但是,没有任何地方描述过在块的开头发生这种情况,而只是在函数的开头描述这种情况。
这些规范的编写方式是,它们描述事情何时发生,而不是何时不发生(这具有一定的逻辑意义),但这意味着要证明某事没有发生,你必须找不到任何这样说的地方确实发生了。