3.8 KiB
Let's examine what exactly happens inside makeArmy
, and the solution will become obvious.
-
It creates an empty array
shooters
:let shooters = [];
-
Fills it in the loop via
shooters.push(function...)
.Every element is a function, so the resulting array looks like this:
shooters = [ function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); }, function () { alert(i); } ];
-
The array is returned from the function.
Then, later, the call to any member, e.g. army[5]()
will get the element army[5]
from the array (that's a function) and call it.
Now why do all such functions show the same value, 10
?
That's because there's no local variable i
inside shooter
functions. When such a function is called, it takes i
from its outer lexical environment.
What will be the value of i
?
If we look at the source:
function makeArmy() {
...
let i = 0;
while (i < 10) {
let shooter = function() { // shooter function
alert( i ); // should show its number
};
...
}
...
}
We can see that all shooter
functions are created in the lexical environment, associated with the one makeArmy()
run. But when army[5]()
is called, makeArmy
has already finished its job, and the final value of i
is 10
(while
finishes at 10
).
As the result, all shooter
functions get the same value from the outer lexical environment and that is, the last value, i=10
.
As you can see above, on each iteration of a while {...}
block, a new lexical environment is created.
So, to fix a problem we can copy the value of i
into a variable within the while {...}
block, like this:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
*!*
let j = i;
*/!*
let shooter = function() { // shooter function
alert( *!*j*/!* ); // should show its number
};
shooters.push(shooter);
i++;
}
return shooters;
}
let army = makeArmy();
// Now the code works correctly
army[0](); // 0
army[5](); // 5
Here let j = i
declares an "iteration-local" variable j
and copies i
into it. Primitives are copied "by value", so we actually get an independent copy of i
, belonging to the current loop iteration.
The shooters work correctly, because, the value of i
now lives a little bit closer. Not in makeArmy()
Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration:
Such problem could also be avoided if we used for
in the beginning, like this:
function makeArmy() {
let shooters = [];
*!*
for(let i = 0; i < 10; i++) {
*/!*
let shooter = function() { // shooter function
alert( i ); // should show its number
};
shooters.push(shooter);
}
return shooters;
}
let army = makeArmy();
army[0](); // 0
army[5](); // 5
That's essentially, the same, as for
on each iteration generates the new lexical environment, with its own variable i
. So shooter
generated in every iteration references its own i
, from that very iteration.
Now, as you've put so much effort into reading this, and the final recipe is so simple - just use for
, you may wonder: was it worth that?
Well, if you could easily answer the question of that task, you wouldn't read the solution, so hopefully this task must have helped you to understand things a bit better.
Besides, there are indeed cases when one prefers while
to for
, and other scenarios where such problems are real.