【问题标题】:JavaScript catch clause scopeJavaScript catch 子句范围
【发布时间】:2011-12-17 02:16:57
【问题描述】:

ECMAScript 5 spec 声明如下:

通常,词法环境与某些特定的 ECMAScript 代码的句法结构,例如 FunctionDeclaration, WithStatement,或 TryStatement 的 Catch 子句和新的词法 每次评估此类代码时都会创建环境。

如果我的理解是正确的,那么当在 JavaScript 中创建一个新的词法环境时,就会进入一个新的作用域,这就是为什么在函数内部声明的变量在函数外部是不可见的:

function example() {
    var x = 10;
    console.log(x); //10
}
console.log(x); //ReferenceError

所以在上面的函数声明中,创建了一个新的词法环境,这意味着x在任何可能存在的外部词法环境中都不可用。

所以上面关于函数声明的引用部分似乎是有道理的。但是,它还声明为 Try 语句的 Catch 子句创建了一个新的词法环境:

try {
    console.log(y); //ReferenceError so we enter catch
} 
catch(e) {
    var x = 10;
    console.log(x); //10
}
console.log(x); //10 - but why is x in scope?

那么catch 块的范围是如何工作的呢?我对什么是词法环境有根本的误解吗?

【问题讨论】:

标签: javascript


【解决方案1】:

由于它在 ES3 中被标准化,catch () {} 子句是(据我所知)唯一一个创建块级范围的 pre-ES2015 javascript 构造,因为它是唯一的块级构造 带有一个参数

这就是它被下一代 javascript 转译器用作 polyfill 来编译它的原因:

ES2015:

{ let priv = 1; }
console.log(priv); // throws ReferenceError

到这里:

ES5 及更低版本:

try { throw void 0 } catch (priv) { priv = 1 }
console.log(priv); // throws ReferenceError

【讨论】:

    【解决方案2】:

    我对这个问题感到困惑,因为我似乎已经遇到过类似的问题,与特定的 catch 块无关,而是在函数定义中。现在我才想起来,实际上几周前我也blogged about that problem :)。

    获取此代码:

    function test(){
      var x = "Hi";
    
      (function(){
        console.log(x);
        var x = "Hi, there!";
      })();
    }
    

    代码:http://jsbin.com/alimuv/2/edit#javascript,live

    输出是什么?通过快速查看它,人们可能会认为它是“Hi”,因为变量 x 的范围取自函数的外部范围。相反,undefined 被打印出来。原因与 catch 块问题的原因相同:lexical scoping,这意味着范围是在 函数定义 而不是在运行时创建的。

    【讨论】:

    • 这里根本不讨论 try/catch。
    【解决方案3】:

    来自dev.opera(已添加重点)

    try-catch-finally 结构相当独特。与其他构造不同,它在运行时在当前范围内创建一个新变量。每次执行 catch 子句时都会发生这种情况,其中捕获的异常对象被分配给一个变量。即使在同一范围内,此变量也不存在于脚本的其他部分中。它在 catch 子句的开头创建,然后在结尾处销毁。

    所以看起来唯一真正在 catch 范围内的是异常本身。其他操作似乎(或保持)绑定到 catch 的外部范围(因此在示例中为全局范围)。

    try {
        console.log(y); //ReferenceError so we enter catch
    }
    catch(e) {
        var x = 10;
        console.log(x); //10
    }
    console.log(e.message); //=> reference error
    

    ES5 中,这些行可能与此相关(加粗/强调):

    1. oldEnv 为正在运行的执行上下文的 LexicalEnvironment
    2. catchEnv 成为调用 NewDeclarativeEnvironment 传递的结果 oldEnv 作为参数。

    在该部分的末尾还指出:

    注意:无论控制如何离开块 LexicalEnvironment 总是恢复到以前的状态

    【讨论】:

    • 是的,我注意到错误对象仅在catch 的范围内。虽然仍然没有意义。我只能假设我仍然有一个完全的误解,或者(可能并不奇怪)规范没有被正确遵循。阅读第 12.14 节(es5.github.com/#x12.14),似乎进一步说在catch 内声明的任何变量都不会在它之外的范围内
    • 我在 ES5 章节中添加了 2 行,标题为“The production Catch ...”。据我从这些行中了解到,只有错误是使用catchEnv (6-8) 在本地范围内限定的。其他内容最后返回oldEnv
    【解决方案4】:

    如果我理解正确,那么它可能意味着,在您的代码中,

    try {
        console.log(y); //ReferenceError so we enter catch
    } 
    catch(e) {
        var x = 10;
        console.log(x); //10
    }
    

    e 只会存在于 catch 块中。在 catch 块外尝试console.log(e);,它会抛出 ReferenceError。

    与 WithStatement 一样,with ({x: 1, y: 2}) { }、x 和 y 将只存在于 with 块内。

    但这并不意味着var 声明将绑定到最近的词法环境。实际上,var 声明会在进入执行上下文时绑定到环境。

    10.5 Declaration Binding Instantiation:当进入执行上下文时,它会查找函数声明、参数和变量声明,然后在执行上下文的变量环境中创建绑定。

    因此,使用var 声明的任何变量都可以在函数中的任何位置访问,而不管控制结构或在函数内部定义的位置如何。请注意,这不包括嵌套函数,因为它们是单独的执行上下文。

    这意味着var 声明将绑定到最近的执行上下文。

    var x = 1;
    (function() {
        x = 5; console.log(x); // 5
        if (false) { var x; }
        x = 9; console.log(x); // 9
    })();
    console.log(x); // 1
    

    所以在上面的代码中,x = 5; 将在内部函数中设置x 变量,因为在函数代码执行之前,if (false) { var x; } 内部的var x; 已经绑定到该函数。

    【讨论】:

    • 啊,这很有意义。 “所以使用var...声明的任何变量”段落有点让它点击。谢谢:)
    • 很好的答案和很好的解释
    • 请注意,即使您在包含 try-catch 的函数顶部声明 ecatch 块中的 e 也不会是同一个变量。和函数参数一样,它是词法上下文的一个新变量。
    • Netbeans(至少 8.0.2 版)错误地警告 catch 子句中的错误标识符是 catch 块中未声明的全局变量。
    猜你喜欢
    • 1970-01-01
    • 2012-02-14
    • 1970-01-01
    • 1970-01-01
    • 2014-12-22
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多