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

使用此代码:

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

  function bar() {
    debugger;
  };
  bar();
}
baz();

我得到这个意想不到的结果:

enter image description here

当我更改代码时:

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

  function bar() {
    x;
    debugger;
  };
  bar();
}

我得到预期的结果:

enter image description here

此外,如果在内部函数中有任何调用,我可以根据需要访问我的变量(无论我传递给什么)。evaleval

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

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

是Chrome的javascript引擎尽可能地“扁平化”功能吗?

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

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

另外,我可以使用断点和语句重现这一点。debugger


答案 1

我找到了一个v8问题报告,它完全是关于你所要求的。

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

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

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

我能想到的唯一解决方案是,每当 devtools 打开时,我们都会取消所有代码,并使用强制的上下文分配进行重新编译。不过,这将在启用devtools的情况下显着降低性能。

下面是一个“如果任何内部函数引用变量,请将其放在上下文对象中”的示例。如果运行 this,您将能够访问该语句,即使该语句仅用于函数,也永远不会调用该语句!xdebuggerxfoo

function baz() {
  var x = "x value";
  var z = "z value";

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

答案 2

就像@Louis说它是由v8优化引起的。您可以将调用堆栈遍历到此变量可见的帧:

call1 call2

或替换为debugger

eval('debugger');

eval将取消当前块