Loops and closures. For and Var

2019-08-21 11:11发布

问题:

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...

回答1:

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
}



回答2:

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.



回答3:

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); 
  };
}