I found many topics explaining this problem, on how I can fix the following code by using var, like this one
http://conceptf1.blogspot.com/2013/11/javascript-closures.html or this one JavaScript closure inside loops – simple practical example.
But I really can't understand why it is not working when using var and working when using let.
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
// outputs 3 3 3
I really have no clue...
ES6's let
is block scope that means it has it's own scope inside {}
like many other traditional languages. But in contrast var
is a global variable in your code.
In first for
loop, function
is just assigned to func[i]
3 times with final value 3 but not executed. If you execute the function inside first loop
, you will get the expected output though like:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
funcs[i](); // execution of func
}
So, the important thing is in which context your function is executing.
Now, by the time funcs[j]()
is executed for the first time in your code, i
's value is already 3
. If you want to log incremented value, you have to pass that as an argument like the following:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(j) { // and store them in funcs
console.log("My value: " + j); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](j); // and now let's run each one to see
}
Unlike to let
, var
is hoisted outside the loop scope. In fact, your i
variable will always equals to the last iteration (which in your example is 3). let
doesn’t have this problem cause it is not hoisted.
Because var
is function-scoped (that is, having the scope of the surrounding function), whereas let
& const
are block-scoped - thus having their own values inside each block (be it if
- or else
-block or each iteration of the loop as in your case). Block-scoped variables are not defined outside the block. Function-scoped variables stay until the end of the function.
Your i
variable is function-scoped, which means once your loop is completed, it's still present outside the first loop & has a value of 3
. So once you call your array function, it takes i
from the upper scope (which is 3
) & outputs it. If you used let
, for example, you'd get a fresh binding for each iteration & your i
's value would keep the initial declaration value.
Edit:
So in order to output sequential values, you need to replace var
with let
:
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
// now `i` is bound to the scope & keeps its initial value
console.log("My value: " + i);
};
}