The following Javascript program:
function f() {
function g() { console.log(x); }
let x = 0;
g(); // prints 0
x = 1;
g(); // prints 1
return g;
}
let g = f();
g(); // prints 1
outputs:
0
1
1
So it seems that g first captures x by reference (since inside f, g() prints 0 then 1 when x is rebound), which means that g closure environment looks something like {'x': x}, and then by value (since outside f, g() prints 1 when x goes out of context at the end of f body), which means that g closure environment looks something like {'x': 1}.
I am trying to relate this behaviour with C++ lambdas which provide capture by reference and by value, but contrary to Javascript, do not allow a capture by reference to outlives the scope of the reference by turning into a capture by value (instead, calling the lambda becomes undefined behaviour).
Is it a correct interpretation of Javascript captures?
If that interpretation is correct, that would explain clearly how captures of block scope variables (let) work in for loops:
let l = [];
for (let x = 0; x < 3; ++x) {
l.push(function () { console.log(x); });
}
l[0](); // prints 0
l[1](); // prints 1
l[2](); // prints 2
In short
You are almost correct except for how it works when it goes out of scope.
More details
How are variables "captured" in JavaScript?
JavaScript uses lexical environments to determine which function uses which variable. Lexical environments are represented by environment records. In your case:
f()defines its lexical environment, in whichxis defined, even if it is afterg();g()defines its lexical environment which is empty.So
g()usesx. Since there is no binding forxthere, JavaScript looks forxin the enclosing environment. Since it is found therein, thexing()will use the binding ofxinf(). This looks like lexically scoped binding.If later you define an
xin the environment whereg()is invoked,g()would still be bound to thexinf():Online demo
This shows that the binding is static and will always refer to the
xknown in the lexical scope whereg()was defined.This excellent article explains in detail how this works, with very nice graphics. It is meant for closures (i.e. anonymous functions with their execution context) but is also applicable to normal functions.
How come that the value of a variable gone out of scope is preserved?
How to explain this very special behavior that JavaScript will always take the current value of
xas long asxremains in scope (like a reference in C++) whereas it will take the last known value whenxis out of scope (when an out of scope reference in C++ would be UB)? Does JavaScript copies the value into the closure when the variable deceases? No, it is simpler than that!This has to do with garbage collection:
g()is returned to an outer context. Sinceg()uses thexinf(), the garbage collector will realize that thisxobject off()is still in use. So, as long asg()is accessible, thexinf()will be kept alive and remain accessible for its still active bindings. So no need to copy the value: thexobject will just stay (unmodified).As a proof that it is not a copy, you can study the following code. It defines a second function in the context of
f()that is able to change the (same)x:Online demo
Edit: Additional bonus article that explains this phenomenon, in a slightly more complex context. Interestingly it explains that this situation can lead to memory leaks if no precaution is taken.