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();
inner
closure does not referencey
, so it is optimized out and the debugger cannot access it.inner2
could, but you're not ininner2
. – Angelesinner
andinner2
have different context and 'y' ininner2
's context but not ininner
's? If it's true,y
cannot be in closure part of scope. And the variabley
can be collected by GC (because onlyinner2
refer to it and inner2 never be used or referred) so the 3rd example of this can't cause memory leak. I meaninner
andinner2
may refer to same context, they share an object containx
andy
. – Hagioscopey
is not referred, so it is optimised out. There is nothing that would require it to be there (and accessible for a debugger). – Arawaky
is referred byinner2
and it's in the scope. My screen shot in question shows that. And ify
is not in the scope, the 3rd example of A surprising javascript memory leak may not cause memory leak. – Hagioscopeinner2
beforereturn inner;
and get the same result. It meansy
is available in the context(Closure part in image) beforefoo
running. (I assume thatinner
andinner2
share the same context of test4. – Hagioscopex
andy
is in the scope of inner.inner
andinner2
share an object in their scope containx
andy
.If u doubt that, this may help. – Hagioscope