为什么在Closure中访问变量时,Chrome调试器会出现undefined?

14

代码:

function test4() {
    var x = 10;
    var y = 100;
    // inner referred x only
    function inner () {
        console.log(x);
        debugger;
    }
    // inner2 referred y to make sure y is in the scope of inner
    function inner2 () {
        console.log(y);
    }
    return inner;
}
var foo = test4();
foo();

y在作用域inner中,即使只有从未使用过的inner2也引用它。我在作用域中检查了结果,xy都在那里。

x and y in closure

但是,当我在监视面板和控制台中检查变量时,无法获取其中的所有变量:

can't get y in watch panel

很奇怪,当使用调试器时,y在作用域中但未定义。这是否意味着调试器不能访问当前上下文中未使用的变量,即使它在闭包中,还是只是一个错误?(我的Chrome版本为51.0.2704.103 m)
这类似于为什么Chrome调试器认为已关闭的局部变量未定义?,但不完全相同。因为我代码中的inner2确保y在闭包中。实际上,我的问题与该问题下Louis's answer的答案相反。

1
并不是这样。区别在于'inner2()'使用了'y'。如果删除'inner2()'部分,'y'将不在闭包中,这就是Gabe的问题的情况。但有趣的是,在那个问题下,Louis的答案表明我的情况不应该是可能的。 - ST_Lighter
6
我很肯定它们是同一件事情。inner闭包没有引用y,因此它被优化掉了,调试器无法访问它。inner2可以访问,但你不在inner2中。 - Alexander O'Mara
4
y 没有被引用,因此它被优化掉了。没有任何需要它存在的东西(也无法通过调试器访问它)。 - zerkms
1
我假设您当前正在inner()中,此时您看不到y。因此,在inner()中可以将其优化掉,并在稍后再次使用。 - andii1997
1
类似的帖子可以在这里找到,看看Louis的回答:https://dev59.com/nV4c5IYBdhLWcg3wCGf2 - N.J.Chen
显示剩余8条评论
1个回答

5

您是内部范围优化机制的第一手观察者。范围优化是检查当前范围中使用哪些变量并优化未使用变量的访问。这样做的原因是因为从JIT编译JavaScript生成的机器代码中,变量命名的整个概念都丢失了。但是,为了保持JavaScript的兼容性,JIT编译器将一个用于局部变量的数组与每个JavaScript函数关联起来。请观察以下代码。

(function(){
  "use strict";
  var myVariable = NaN; // |Ref1|
  var scopedOne = (function(){
    var myVariable = 101; // |Ref2|
    return x => x * myVariable;
  })();
  var scopedTwo = (function(){
    var myVariable = -7; // |Ref3|
    return x => x / myVariable;
  })();
  console.log("scopedOne(2):  ", scopedOne(2));
  console.log("scopedTwo(56): ", scopedTwo(56))
})();

如上所述,Javascript是一种有作用域的基于堆栈的语言。如果Javascript不是一个有作用域的语言,那么函数中使用的变量将依赖于执行函数时变量位置上的值。例如,在没有作用域的情况下,“scopedOne”将使用“|Ref1|”(NaN)处的“myVariable”的值而不是在“|Ref2|”(101)处的值,并将“NaN”记录到控制台中。回到主要观点,在机器代码中,当调试器进入时,它只能确定已使用变量的实际内存位置,因为只有这些内存位置已持久化到机器代码中,因为只有这些变量被使用。其余变量的内存位置对调试器来说仍然是个谜。正如您所观察到的那样,这具有使作用域中未使用的变量对该函数“不可见”的副作用。但是,有一个解决方案。
为了规避这个问题,只需将“debugger;”语句包装在eval中,强制浏览器进行昂贵的变量查找,以查找作用域中的所有变量。基本上,浏览器必须返回原始源代码,检查其中作用域变量的原始名称,并确定由JIT生成的机器代码存储变量值的位置。打开开发者工具并运行下面的代码段。然后转到“调用堆栈”面板中的前一个级别,观察变量“y”的可见性如何从在eval内部的状态更改为在eval外部不可见的状态。

function test4() {
    var x = 10;
    var y = 100;
    // inner referred x only
    function inner () {
        console.log(x);
        eval("debugger;");
    }
    // inner2 referred y to make sure y is in the scope of inner
    function inner2 () {
        console.log(y);
    }
    return inner;
}
var foo = test4();
foo();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接