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 either i
or j
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 the for
loop. Your second example just has one variable that is common to all revs of your for
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:
var j;
for (var i = 0; i < 10; i++)
{
j = i;
process.nextTick(function ()
{
console.log(j)
})
}
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.
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 variable j
(and i
, which always have the same value when the anonymous function is created). Each of these functions use the value of j
at a time after the outer for-loop has run entirely, so j=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 of j
, 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 of i
into a function-local scope by calling the wrapper function (and incidentally shadowing the old variable i
into the function parameter i
), you capture the value of the variable i
at that moment, instead of retaining the enclosed reference to i
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 that x
takes the value in i
at the moment the anonymous function is called so it will get each of the values in the for-loop (1..10).
The Mozilla Developer Network has a very nice write-up:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures