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

Gab*_*ley 155 javascript google-chrome google-chrome-devtools

使用此代码:

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

  function bar() {
    debugger;
  };
  bar();
}
baz();
Run Code Online (Sandbox Code Playgroud)

我得到了这个意外的结果:

在此输入图像描述

当我更改代码时:

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

  function bar() {
    x;
    debugger;
  };
  bar();
}
Run Code Online (Sandbox Code Playgroud)

我得到了预期的结果:

在此输入图像描述

此外,如果eval在内部函数中有任何调用,我可以按照我想要的方式访问我的变量(无论我传递给什么都没关系eval).

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

与Chrome有什么关系,调试器的行为不如Firefox?我已经观察了这种行为一段时间,包括版本41.0.2272.43 beta(64位).

是不是Chrome的javascript引擎可以"平坦化"这些功能呢?

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

我知道在使用交互式调试器时经常有范围和变量定义的怪癖,但在我看来,基于语言规范,应该是这些怪癖的"最佳"解决方案.所以我很好奇这是因为Chrome比Firefox更优化.以及在开发期间是否可以轻松禁用这些优化(也许在开放工具打开时应该禁用它们?).

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

Lou*_*uis 143

我发现了一个v8 问题报告,这正是你所要求的.

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

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

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

我能想到的唯一解决方案是,无论何时启用devtools,我们都会取消所有代码并使用强制上下文分配重新编译.尽管如此,这将极大地降低性能.

这是"如果任何内部函数引用变量,将其放在上下文对象中"的示例.如果你运行它,你将能够xdebugger语句中访问,即使x只在foo函数中使用,它从未被调用过!

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();
Run Code Online (Sandbox Code Playgroud)

  • 你有没有找到一种方法来取代代码?我喜欢使用调试器作为REPL并在那里编码然后将代码传输到我自己的文件.但它通常是不可行的,因为应该存在的变量无法访问.一个简单的eval不会这样做.我听到无限循环可能. (12认同)
  • 该问题的最后一条评论说:*将V8置于一个强制上下文分配的模式是可能的,但我不确定如何/何时通过Devtools UI触发*为了调试,我有时想要这样做.我怎么强迫这种模式? (5认同)
  • @user208769 当关闭重复时,我们倾向于对未来读者最有用的问题。有多种因素可以帮助确定哪个问题最有用:您的问题得到了 0 个答案,而这个问题得到了多个赞成的答案。所以这个问题是两个中最有用的。仅当实用性基本相同时,日期才成为决定性因素。 (2认同)
  • 这个答案回答了实际问题(为什么?),但隐含的问题是——如何访问未使用的上下文变量进行调试,而无需在我的代码中添加对它们的额外引用?-- 下面的@OwnageIsMagic 给出了更好的回答。 (2认同)

Own*_*gic 29

就像@Louis所说的那样是由v8优化造成的.您可以遍历调用堆栈到此变量可见的框架:

CALL1 CALL2

或者替换debugger

eval('debugger');
Run Code Online (Sandbox Code Playgroud)

eval 将取消当前的块


Dav*_*ipe 6

我在nodejs中也注意到了这一点.我相信(我承认这只是一个猜测),当编译代码时,如果x没有出现在内部bar,它就不会x在范围内提供bar.这可能会使它更有效率; 问题是有人忘了(或不关心),即使没有有xbar,你可能决定运行调试,因此仍然需要访问x来自内部bar.

  • 这不是重点.使用调试器时,我经常遇到一种情况,我想知道外部作用域中变量的值,但不能因为这个原因.在一个更哲学的说明中,我会说调试器在撒谎.变量是否存在于内部范围中不应取决于它是否实际使用或是否存在无关的`eval`命令.如果声明了变量,则应该可以访问它. (6认同)
  • 谢谢.基本上我希望能够比"调试器所说的"更好地向javascript初学者解释这个. (2认同)
  • @GabeKopley:从技术上讲,调试器并没有说谎。如果一个变量没有被引用,那么它在技术上就不是封闭的。因此解释器不需要创建闭包。 (2认同)