Is there a definite source on variable capture in Javascript besides the standard (it's a pain to read the standard)?
In the following code i
is copied by value:
for (var i = 0; i < 10; i++)
{
(function (i)
{
process.nextTick(function ()
{
console.log(i)
})
}) (i)
}
So it prints 1..10. process.nextTick
is an analog of setTimeout(f,0)
in node.
But in the next code i doesn't seem to be copied:
for (var i = 0; i < 10; i++)
{
var j = i
process.nextTick(function ()
{
console.log(j)
})
}
It prints 9 10 times. Why? I'm more interested in a reference/general article than in explaining this concrete case of capture.
I don't have a handy reference. But the bottom line is: In the first, you're explicitly passing in
i
to an anonymous function, which creates a new scope. You are not creating a new scope for eitheri
orj
in the second. Also, JavaScript always captures variables, not values. So you would be able to modify i too.The JavaScript
var
keyword has function scope, not block scope. So a for loop does not create a scope.As a note, the non-standard
let
keyword has local scope.It is copied (or assigned) in your second example, it's just that there's only one copy of variable
j
and it will have the value that it last had in it which will be 9 (the last rev of your for loop). You need a new function closure to create a new copy of a variable for each rev of thefor
loop. Your second example just has one variable that is common to all revs of yourfor
loop, thus it can only have one value.I don't know of any definitive writeup on this topic.
Variables in javascript are scoped to the function level. There is no block scoping in javascript. As such, if you want a new version of a variable for each rev of the for loop, you have to use a new function (creating a function closure) to capture that new value each time through the
for
loop. Without the function closure, the one variable will just have one value that will be common to all users of that variable.When you declare a variable such as your
var j = i;
at some location other than the beginning of the function, javascript hoists the definition to the top of the function and your code becomes equivalent to this:This is called
variable hoisting
and is a term you could Google if you want to read more about it. But, the point is that there is only function scope so a variable declared anywhere in a function is actually declared once at the top of the function and then assigned to anywhere in the function.The Mozilla Developer Network has a very nice write-up:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
In JavaScript, functions enclose variables which were defined in a scope outside of their own in such a way that they have a "living" reference to the variable, not a snapshot of its value at any particular time.
So in your second example, you create ten anonymous functions (in
process.nextTick(function(){...})
) which enclose the variablej
(andi
, which always have the same value when the anonymous function is created). Each of these functions use the value ofj
at a time after the outer for-loop has run entirely, soj=i=10
at the time that each of the functions is called. That is, first your for-loop runs entirely, then your anonymous functions run and use the value ofj
, which is already set to 10!In your first example, the situation is a little different. By wrapping the call to
process.nextTick(...)
in it's own anonymous function and by binding the value ofi
into a function-local scope by calling the wrapper function (and incidentally shadowing the old variablei
into the function parameteri
), you capture the value of the variablei
at that moment, instead of retaining the enclosed reference toi
whose value changes in the enclosure of the inner anonymous functions.To clarify your first example somewhat, try changing the anonymous wrapper function to use an argument named
x
((function (x) { process.nextTick(...); })(i)
). Here we clearly see thatx
takes the value ini
at the moment the anonymous function is called so it will get each of the values in the for-loop (1..10).