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
You are almost correct except for how it works when it goes out of scope.
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 which x
is defined, even if it is after g()
;g()
defines its lexical environment which is empty.So g()
uses x
. Since there is no binding for x
there, JavaScript looks for x
in the enclosing environment. Since it is found therein, the x
in g()
will use the binding of x
in f()
. This looks like lexically scoped binding.
If later you define an x
in the environment where g()
is invoked, g()
would still be bound to the x
in f()
:
function f() {
function g() { console.log(x); }
let x = 0;
g(); // prints 0
x = 1;
g(); // prints 1
return g;
}
let x = 4;
let g = f();
g(); // prints 1 (the last known value in f before returning)
This shows that the binding is static and will always refer to the x
known in the lexical scope where g()
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 to explain this very special behavior that JavaScript will always take the current value of x
as long as x
remains in scope (like a reference in C++) whereas it will take the last known value when x
is 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. Since g()
uses the x
in f()
, the garbage collector will realize that this x
object of f()
is still in use. So, as long as g()
is accessible, the x
in f()
will be kept alive and remain accessible for its still active bindings. So no need to copy the value: the x
object 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
:
let h;
function f() {
function g() { console.log(x); }
h = function () { x = 27; }
let x = 0;
g(); // prints 0
x = 1;
g(); // prints 1
x = 3;
return g;
}
let x = 4;
let g = f();
g(); // prints 3
h();
g(); // prints 27
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.