Javascript 为什么Chrome调试器认为闭合局部变量未定义?

Javascript 为什么Chrome调试器认为闭合局部变量未定义?,javascript,google-chrome,google-chrome-devtools,Javascript,Google Chrome,Google Chrome Devtools,使用此代码: function baz() { var x = "foo"; function bar() { debugger; }; bar(); } baz(); 我得到了一个意想不到的结果: 当我更改代码时: function baz() { var x = "foo"; function bar() { x; debugger; }; bar(); } 我得到了预期的结果: 此外,如果在内部函数中有任何对eval的调用

使用此代码:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
  };
  bar();
}
baz();
我得到了一个意想不到的结果:

当我更改代码时:

function baz() {
  var x = "foo";

  function bar() {
    x;
    debugger;
  };
  bar();
}
我得到了预期的结果:

此外,如果在内部函数中有任何对
eval
的调用,我可以按照自己的意愿访问变量(不管我传递给
eval

同时,Firefox开发工具在这两种情况下都提供了预期的行为

Chrome是怎么回事,调试器的行为不如Firefox方便?在41.0.2272.43 beta版(64位)之前,我已经观察到这种行为一段时间了

是不是Chrome的javascript引擎在可能的情况下“扁平化”了函数

有趣的是,如果我添加在内部函数中引用的第二个变量,
x
变量仍然没有定义

我知道在使用交互式调试器时,在作用域和变量定义方面经常存在一些怪癖,但在我看来,基于语言规范,应该有一个“最佳”解决方案来解决这些怪癖。所以我很好奇这是否是因为Chrome比Firefox优化得更远。以及这些优化是否可以在开发过程中轻松禁用(也许应该在开发工具打开时禁用?)


此外,我还可以使用断点以及
调试器
语句来重现这一点。

我怀疑这与变量和函数有关。JavaScript将所有变量和函数声明放在定义它们的函数的顶部。更多信息请点击此处:

我打赌Chrome调用断点时变量对作用域不可用,因为函数中没有其他内容。这似乎有效:

函数baz(){
var x=“foo”;
功能条(){
控制台日志(x);
调试器;
};
bar();

}
我怀疑这与变量和函数有关。JavaScript将所有变量和函数声明放在定义它们的函数的顶部。更多信息请点击此处:

我打赌Chrome调用断点时变量对作用域不可用,因为函数中没有其他内容。这似乎有效:

函数baz(){
var x=“foo”;
功能条(){
控制台日志(x);
调试器;
};
bar();

}
我在nodejs中也注意到了这一点。我相信(我承认这只是一个猜测),当代码被编译时,如果
x
没有出现在
bar
中,那么
x
就不能在
bar
的范围内使用。这可能会使它稍微更有效率;问题是有人忘记(或不在乎)即使
bar
中没有
x
,您可能会决定运行调试器,因此仍然需要从内部
bar
访问
x
,我在nodejs中也注意到了这一点。我相信(我承认这只是一个猜测),当代码被编译时,如果
x
没有出现在
bar
中,那么
x
就不能在
bar
的范围内使用。这可能会使它稍微更有效率;问题是有人忘记(或不在乎)即使
bar
中没有
x
,您可能决定运行调试器,因此仍然需要从
bar
内部访问
x
,哇,真有趣

正如其他人提到的,这似乎与
范围相关,但更具体地说,与
调试器范围相关。在开发人员工具中评估注入的脚本时,它似乎确定了一个
范围链
,这导致了一些奇怪之处(因为它绑定到了检查器/调试器范围)。您发布内容的一个变体是:

(编辑-事实上,你在最初的问题中提到了这一点,我的错!)

对于雄心勃勃的人和/或好奇的人,请查看来源,看看发生了什么:


哇,真有趣

正如其他人提到的,这似乎与
范围相关,但更具体地说,与
调试器范围相关。在开发人员工具中评估注入的脚本时,它似乎确定了一个
范围链
,这导致了一些奇怪之处(因为它绑定到了检查器/调试器范围)。您发布内容的一个变体是:

(编辑-事实上,你在最初的问题中提到了这一点,我的错!)

对于雄心勃勃的人和/或好奇的人,请查看来源,看看发生了什么:

我发现了一款v8引擎,它正是您想要的

现在,总结一下问题报告中的内容。。。v8可以将函数的局部变量存储在堆栈或堆上的“上下文”对象中。只要函数不包含引用它们的任何内部函数,它就会在堆栈上分配局部变量这是一种优化。如果任何内部函数引用局部变量,则该变量将放在上下文对象中(即堆上而不是堆栈上)。
eval
的情况很特殊:如果内部函数调用它,则所有局部变量都放在上下文对象中

使用context对象的原因是,通常可以从外部函数返回内部函数,然后外部函数运行时存在的堆栈将不再可用。因此,内部函数访问的任何内容都必须在外部函数中生存,并在堆中而不是堆栈中生存

调试器无法检查堆栈上的那些变量。关于调试中遇到的问题,一名项目成员:

我能想到的唯一解决方案是,无论何时打开devtools,我们都会对所有代码进行除臭,并使用强制上下文分配重新编译。这将大大降低启用devtools后的性能
function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();
function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
eval('debugger');