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

时间:2022-12-17 16:49:12

With this code:

使用此代码:

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

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

I get this unexpected result:

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

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

When I change the code:

当我更改代码时:

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

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

I get the expected result:

我得到了预期的结果:

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

Also, if there is any call to eval within the inner function, I can access my variable as I want to do (doesn't matter what I pass to eval).

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

Meanwhile, Firefox dev tools give the expected behavior in both circumstances.

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

What's up with Chrome that the debugger behaves less conveniently than Firefox? I have observed this behavior for some time, up to and including Version 41.0.2272.43 beta (64-bit).

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

Is it that Chrome's javascript engine "flattens" the functions when it can?

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

Interestingly if I add a second variable that is referenced in the inner function, the x variable is still undefined.

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

I understand that there are often quirks with scope and variable definition when using an interactive debugger, but it seems to me that based on the language specification there ought to be a "best" solution to these quirks. So I am very curious if this is due to Chrome optimizing further than Firefox. And also whether or not these optimizations can easily be disabled during development (maybe they ought to be disabled when dev tools are open?).

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

Also, I can reproduce this with breakpoints as well as the debugger statement.

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

5 个解决方案

#1


108  

I've found a v8 issue report which is precisely about what you're asking.

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

Now, To summarize what is said in that issue report... v8 can store the variables that are local to a function on the stack or in a "context" object which lives on the heap. It will allocate local variables on the stack so long as the function does not contain any inner function that refers to them. It is an optimization. If any inner function refers to a local variable, this variable will be put in a context object (i.e. on the heap instead of on the stack). The case of eval is special: if it is called at all by an inner function, all local variables are put in the context object.

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

The reason for the context object is that in general you could return an inner function from the outer one and then the stack that existed while the outer function ran won't be available anymore. So anything the inner function accesses has to survive the outer function and live on the heap rather than on the stack.

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

The debugger cannot inspect those variables that are on the stack. Regarding the problem encountered in debugging, one Project Member says:

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

The only solution I could think of is that whenever devtools is on, we would deopt all code and recompile with forced context allocation. That would dramatically regress performance with devtools enabled though.

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

Here's an example of the "if any inner function refers to the variable, put it in a context object". If you run this you'll be able to access x at the debugger statement even though x is only used in the foo function, which is never called!

这是“如果任何内部函数引用变量,将其放在上下文对象中”的示例。如果你运行它,你将能够在调试器语句中访问x,即使x仅用于foo函数,它永远不会被调用!

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

#2


13  

Like @Louis said it caused by v8 optimizations. You can traverse Call stack to frame where this variable is visible:

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

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

Or replace debugger with

或者用。替换调试器

eval('debugger');

eval will deopt current chunk

eval将取消当前的块

#3


6  

I've also noticed this in nodejs. I believe (and I admit this is only a guess) that when the code is compiled, if x does not appear inside bar, it doesn't make x available inside the scope of bar. This probably makes it slightly more efficient; the problem is someone forgot (or didn't care) that even if there's no x in bar, you might decide to run the debugger and hence still need to access x from inside bar.

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

#4


2  

Wow, really interesting!

哇,真有意思!

As others have mentioned, this seems to be related to scope, but more specifically, related to debugger scope. When injected script is evaluated in the developer tools, it seems to determine a ScopeChain, which results in some quirkiness (since it's bound to the inspector/debugger scope). A variation of what you posted is this:

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

(EDIT - actually, you mention this in your original question, yikes, my bad!)

(编辑 - 实际上,你在原来的问题中提到这个,哎呀,我的坏!)

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

For the ambitious and/or curious, scope (heh) out the source to see what's going on:

对于雄心勃勃和/或好奇,范围(heh)的消息来源,看看发生了什么:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

#5


0  

I suspect this has to do with variable and function hoisting. JavaScript brings all variable and function declarations to the top of the function they are defined in. More info here: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

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

I bet that Chrome is calling the break point with the variable unavailable to the scope because there is nothing else in the function. This seems to work:

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

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

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

As does this:

就像这样:

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

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

Hope this, and / or the link above helps. These are my favorite kind of SO questions, BTW :)

希望这个,和/或上面的链接有帮助。这些是我最喜欢的SO问题,BTW :)

#1


108  

I've found a v8 issue report which is precisely about what you're asking.

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

Now, To summarize what is said in that issue report... v8 can store the variables that are local to a function on the stack or in a "context" object which lives on the heap. It will allocate local variables on the stack so long as the function does not contain any inner function that refers to them. It is an optimization. If any inner function refers to a local variable, this variable will be put in a context object (i.e. on the heap instead of on the stack). The case of eval is special: if it is called at all by an inner function, all local variables are put in the context object.

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

The reason for the context object is that in general you could return an inner function from the outer one and then the stack that existed while the outer function ran won't be available anymore. So anything the inner function accesses has to survive the outer function and live on the heap rather than on the stack.

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

The debugger cannot inspect those variables that are on the stack. Regarding the problem encountered in debugging, one Project Member says:

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

The only solution I could think of is that whenever devtools is on, we would deopt all code and recompile with forced context allocation. That would dramatically regress performance with devtools enabled though.

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

Here's an example of the "if any inner function refers to the variable, put it in a context object". If you run this you'll be able to access x at the debugger statement even though x is only used in the foo function, which is never called!

这是“如果任何内部函数引用变量,将其放在上下文对象中”的示例。如果你运行它,你将能够在调试器语句中访问x,即使x仅用于foo函数,它永远不会被调用!

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

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

  function bar() {
    debugger;
  };

  bar();
}
baz();

#2


13  

Like @Louis said it caused by v8 optimizations. You can traverse Call stack to frame where this variable is visible:

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

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

Or replace debugger with

或者用。替换调试器

eval('debugger');

eval will deopt current chunk

eval将取消当前的块

#3


6  

I've also noticed this in nodejs. I believe (and I admit this is only a guess) that when the code is compiled, if x does not appear inside bar, it doesn't make x available inside the scope of bar. This probably makes it slightly more efficient; the problem is someone forgot (or didn't care) that even if there's no x in bar, you might decide to run the debugger and hence still need to access x from inside bar.

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

#4


2  

Wow, really interesting!

哇,真有意思!

As others have mentioned, this seems to be related to scope, but more specifically, related to debugger scope. When injected script is evaluated in the developer tools, it seems to determine a ScopeChain, which results in some quirkiness (since it's bound to the inspector/debugger scope). A variation of what you posted is this:

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

(EDIT - actually, you mention this in your original question, yikes, my bad!)

(编辑 - 实际上,你在原来的问题中提到这个,哎呀,我的坏!)

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

For the ambitious and/or curious, scope (heh) out the source to see what's going on:

对于雄心勃勃和/或好奇,范围(heh)的消息来源,看看发生了什么:

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

#5


0  

I suspect this has to do with variable and function hoisting. JavaScript brings all variable and function declarations to the top of the function they are defined in. More info here: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

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

I bet that Chrome is calling the break point with the variable unavailable to the scope because there is nothing else in the function. This seems to work:

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

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

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

As does this:

就像这样:

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

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

Hope this, and / or the link above helps. These are my favorite kind of SO questions, BTW :)

希望这个,和/或上面的链接有帮助。这些是我最喜欢的SO问题,BTW :)