为什么 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

另外,如果在内部函数中有对 eval的调用,我可以按照自己的意愿访问变量(不管我传递给 eval的是什么)。

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

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

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

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

我知道在使用交互式调试器时,在范围和变量定义方面经常会出现一些问题,但在我看来,基于语言规范,应该有一个“最佳”的解决方案来解决这些问题。所以我很好奇这是否是因为 Chrome 比 Firefox 优化得更多的缘故。还有这些优化是否可以在开发期间轻松禁用(也许在开发工具打开时应该禁用它们?).

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

39974 次浏览

我怀疑这与变量和函数提升有关。JavaScript 将所有变量和函数声明放在定义它们的函数的顶部。更多信息请点击: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

我敢打赌,Chrome 正在调用范围中不可用变量的断点,因为函数中没有其他内容。这似乎行得通:

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


function bar() {
console.log(x);
debugger;
};
bar();
}

就像这样:

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


function bar() {
debugger;
console.log(x);
};
bar();
}

希望这个,或者上面的链接能有所帮助。顺便说一下,这是我最喜欢的 SO 类问题:)

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

哇,真有意思!

正如其他人所提到的,这似乎与 scope有关,但更具体地说,与 debugger scope有关。当注入的脚本在开发人员工具中进行评估时,它似乎确定了一个 ScopeChain,这导致了一些古怪的结果(因为它绑定到了检查器/调试器范围)。你发布的内容的一个变体是这样的:

(编辑——实际上,你在你的原始问题 哎呀,我的错!中提到了这一点)

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();

对于那些有雄心壮志或者好奇心很强的人来说,可以从源头上看看到底发生了什么:

Https://github.com/webkit/webkit/tree/master/source/javascriptcore/inspector Https://github.com/webkit/webkit/tree/master/source/javascriptcore/debugger

我找到了一台 V8 发表报告正好符合你的要求。

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

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

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

我能想到的唯一解决方案是,无论什么时候启用 devtools,我们都会删除所有代码并使用强制上下文分配重新编译。不过,如果启用了 devtools,性能将急剧下降。

下面是一个“如果任何内部函数引用了变量,将其放在上下文对象中”的例子。如果您运行此命令,您将能够在 debugger语句中访问 x,即使 x只用于 foo函数 从来没有被称为

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


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


function bar() {
debugger;
};


bar();
}
baz();

就像@Louis 说的,它是由 v8优化引起的。 您可以遍历 Call 堆栈到框架中这个变量可见的地方:

call1 call2

或将 debugger替换为

eval('debugger');

eval将提取当前块

我好像能进入 _this。对我来说,在铬检查器中没有定义 this的地方,_this似乎引用了适当的上下文(可能是在堆栈跟踪检查器中用作 > local > this的内容?).