为什么 catch 子句有自己的词法环境?

IT技术 javascript scope try-catch ecmascript-5 hoisting
2021-01-27 03:01:07

考虑以下来自 ECMA-262 v5.1 的摘录(我最近在这个问题中看到):

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

我认为这意味着catch子句主体会像函数一样提升自己的变量,但显然情况并非如此

var a = 1;
try {
    console.log(x); // ReferenceError
} catch(ex) {
    console.log(a); // 1, not undefined
    var a = 3;
}

有人知道为什么吗?另外,为什么catch子句需要自己的词法环境?

1个回答

是的,catch子句确实有自己的词法环境。检查评估时会发生什么:它创建一个新的(从当前的派生)并将异常标识符绑定到它。当执行 catch 块时,当前的执行上下文 LexicalEnvironment被切换到新的执行上下文,而VariableEnvironment("其环境记录包含由VariableStatementsFunctionDeclarations"创建的绑定) 保持不变。

console.log(a); // undefined - declared from within the catch,
                // but in the current VariableEnvironment
a = 1;
console.log(typeof ex); // undefined - no binding
try {
    console.log(ex); // a ReferenceError in this LexicalEnvironment
} catch (ex) { // introducing the new LexicalEnvironment
    console.log(ex); // …and it works here!
    var a = 3; // variable declaration
}

有趣的事实:如果您尝试子句中声明函数catch(尽管在块中语法无效,但通常会接受“函数声明语句”),它的作用域将变为当前作用域,VariableEnvironment因此将无法访问异常:

try {throw "some"} catch(x) { function y(){console.log(x, typeof x);} y(); }
                    // throws a ReferenceError for x   ^

(更新:这在 ES6 中不再适用,其中块级函数声明有效并且在块范围内关闭)

既然您这么说了,那么异常对象需要一个单独的记录就完全有道理了。当你说VariableEnvironment保持不变时,是因为规范没有说应该创建一个新的,还是明确说明它应该保持不变?
2021-03-23 03:01:07
如果规范。没有明确说某事改变了,这相当于说这件事保持不变。一般来说,我们不会费心去明确“保持不变”,因为这是默认设置。
2021-03-30 03:01:07
@D_S_X 啊,我猜这是另一种可能性,iirc Python 做到了。但是虽然我不知道设计者的选择,但对我来说似乎是合理的:标识符应该只在它具有值的块中可用。是的,在 ES6 中,每个块无论如何都会创建一个新的词法环境。
2021-03-30 03:01:07
是的,它没有明确说明,但由于词法环境是执行上下文的一个独特组件,我看不出什么会改变变量环境 - 并且没有建立新的上下文,只有当前的上下文被修改(并重置)块执行后)。
2021-04-03 03:01:07
谢谢,现在清楚了。我了解到变量环境在同一个执行上下文中永远不会改变,所以我期望的效果catch是不可能的。它发生在函数中,因为它们创建了全新的执行上下文。
2021-04-11 03:01:07