The key problem you're facing is that a closure capture variables by reference, not by value. For example:
var i = 0;
var f = function(){ i++; console.log(i); }
var g = function(){ i++; console.log(i); }
f(); g(); f(); g();
this will display 1, 2, 3, 4 because f
and g
are not capturing the value of the variable i
, but the variable itself and therefore are both mutating the same variable.
A very common mistake is to create multiple closures in a loop forgetting that all of them will capture the same variable (the one used in the loop) and thus that when this loop variable is incremented all the already created closures will see it changing. For example in your code
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
var item = "item" + i; // inside
console.log("item: " + item + ", i: " + i);
};
}
item
is a variable local to the function but i
instead comes from the outside and it's shared between all of them. This can be seen for example with
i = 42;
funcs[0](); funcs[1](); funcs[2](); // Displays 42 three times
i = 99;
funcs[0](); funcs[1](); funcs[2](); // Now they all display 99
In the second case of your question things are just slightly different because in
for (var i = 0; i < 3; i++) {
var item = "item" + i;
funcs[i] = function() {
console.log("item: " + item + ", i: " + i);
};
}
also the item
variable is now shared as it's defined outside the function and the closure captures it. In the display you see the last value that's assigned to it.
The key point to remember that in Javascript a variable is local to the function, not local to the {...}
block (like it's in C++ for example) and thus the discriminating factor is if the variables are declared inside the function or outside the function, not if they're declared inside or outside the loop.
To overcome this common problem and have a separate index for each of the closures created in a loop often you will see code like
var f = (function(i){return function(){ ... };})(i);
where a function is created and immediately called to return another function.
The reason is that the only way to create a separate new variable is a function, leaving us with the pattern of creating a function that returns a function: the useful closure (the inner) in this case will capture a variable that is different for each of the iterations (because it's a parameter of the outer).
Changing your code to
for (var i = 0; i < 3; i++) {
funcs[i] = (function(i){return function() {
var item = "item" + i;
console.log("item: " + item + ", i: " + i);
};})(i);
}
Will work as expected (i.e. each function will now have its own i
value).
EDIT
In current Javascript all the above applies to var
variables (that are function scoped) but you can also use let
variable that are instead block scoped.
This means the code can now be simplified and for example with:
lambdas = [];
for (let i=0; i<10; i++) {
let j = i*i; // Variable j is block scoped
lambdas.push(function(){ return j; });
}
console.log(lambdas[3]()); // ==> output 9
console.log(lambdas[5]()); // ==> output 25
(with var j = i*i;
instead you'll get 81 for both)