Why does Chrome debugger get undefined when accessing variables in Closure? [duplicate]
Asked Answered
H

1

14

Code:

function test4() {
    var x = 10;
    var y = 100;
    // inner referred x only
    function inner () {
        console.log(x);
        debugger;
    }
    // inner2 referred y to make sure y is in the scope of inner
    function inner2 () {
        console.log(y);
    }
    return inner;
}
var foo = test4();
foo();

y is in the scope of inner even only inner2 which never been used refer to it. I checked the result in scope and x, y are there:

x and y in closure

But when I checked variables in watch panel and console, I can't get all of them:

can't get y in watch panel

It's weird that y is in the scope but get not defined when using debugger. So, is it means that debugger can not access variable that not used in current context even it's in the closure or it just a bug? (My chrome version is 51.0.2704.103 m)

It's similar to Why does Chrome debugger think closed local variable is undefined? but not the same. Because inner2 in my code make sure that y is in the closure. And actually my question is opposite to Louis's answer under that question.

Hagioscope answered 7/9, 2016 at 4:17 Comment(13)
Not actually. The difference is 'inner2()' used 'y'. If remove 'inner2()' part, 'y' will not in closure and it's the situation of Gabe's question. But it's interesting that Louis's answer under that question shows that my situation shouldn't be possible.Hagioscope
I'm pretty sure it's the same thing. The inner closure does not reference y, so it is optimized out and the debugger cannot access it. inner2 could, but you're not in inner2.Angeles
So you means inner and inner2 have different context and 'y' in inner2's context but not in inner's? If it's true, y cannot be in closure part of scope. And the variable y can be collected by GC (because only inner2 refer to it and inner2 never be used or referred) so the 3rd example of this can't cause memory leak. I mean inner and inner2 may refer to same context, they share an object contain x and y.Hagioscope
Hmm, I suspect there may be something else at play in the way it is optimized. Perhaps you could expand upon your question to include some of this information and clarify it for potential answers?Angeles
I wish I could. Actually I'm new here and I can't give more than 2 links in the question because of reputation.Hagioscope
y is not referred, so it is optimised out. There is nothing that would require it to be there (and accessible for a debugger).Arawak
y is referred by inner2 and it's in the scope. My screen shot in question shows that. And if y is not in the scope, the 3rd example of A surprising javascript memory leak may not cause memory leak.Hagioscope
I assume that you are currently in inner() at the time you don't see y. So it can be optimized out in inner() and can be available again later.Gallaher
This is expected behavior.Niemann
@Gallaher I run inner2 before return inner; and get the same result. It means y is available in the context(Closure part in image) before foo running. (I assume that inner and inner2 share the same context of test4.Hagioscope
It may be in the context of test4, but it could be that it is not in the scope of inner. I don't know the exact expected behavior of JS in this case. But where is the problem? As i see it works as you need it.Gallaher
@Gallaher I think the image shows that both x and y is in the scope of inner. inner and inner2 share an object in their scope contain x and y .If u doubt that, this may help.Hagioscope
Similar post can be found here, check out Louis's response #28389030Brittain
B
5

You are a first-hand observer of the internal mechanics of scope-optimization. Scope optimization is checking to see which variables are used in the current scope and optimizing out access to unused variables. The reason for this is because in the machine code generated from JIT compilation of javascript, the whole concept of variable naming is lost. But, to maintain javascript compliance, the JIT compiler associates an array of used local variables to each javascript function. Observe the following code.

(function(){
  "use strict";
  var myVariable = NaN; // |Ref1|
  var scopedOne = (function(){
    var myVariable = 101; // |Ref2|
    return x => x * myVariable;
  })();
  var scopedTwo = (function(){
    var myVariable = -7; // |Ref3|
    return x => x / myVariable;
  })();
  console.log("scopedOne(2):  ", scopedOne(2));
  console.log("scopedTwo(56): ", scopedTwo(56))
})();

As seen above, Javascript is a scoped stack-based language. If Javascript was not a scoped language, then the variables used in the functions would depend on the values of the variables at the location where the function was being executed. For instance, without a scope, scopedOne would use the value of myVariable at |Ref1| (NaN) instead of at |Ref2| (101) and would log NaN to the console. Back to the main point, in the machine code, when the debugger comes in, it can only figure out where the actual locations in memory are of the used variables since only those memory locations have persisted to machine code as only those variable have been used. The memory locations of the rest of the variables remain a mystery to it. As you have observed, this has the secondary side-effect of making unused variables in the scope "invisible" to that function. However, there is a solution.

To circumvent this problem, simply wrap the debugger; statement in an eval to force the browser to do an expensive variable lookup of all the variables in the scope. Basically, the browser has to go back to the original source code, examine it for the original names of the variables in the scope, and figure out where the values of the variables are stored by the JIT-generated machine code. Open up developer tools and run the snippet below. Then go to the prior level in the "Call Stack" panel and observe how the visibility of the value of the variable y changes from visible inside eval to invisible outside eval.

function test4() {
    var x = 10;
    var y = 100;
    // inner referred x only
    function inner () {
        console.log(x);
        eval("debugger;");
    }
    // inner2 referred y to make sure y is in the scope of inner
    function inner2 () {
        console.log(y);
    }
    return inner;
}
var foo = test4();
foo();
Broider answered 10/5, 2018 at 20:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.