I have this code:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function () {
console.log(i);
// that should show its number
};
shooters.push(shooter); // and add it to the array
i++;
}
// ...and return the array of shooters
return shooters;
}
let army = makeArmy();
// all shooters show 10 instead of their numbers 0, 1, 2, 3...
army[0](); // 10 from the shooter number 0
army[1](); // 10 from the shooter number 1
army[2](); // 10 ...and so on.
This happens because i
is always a reference to the outer i
inside the makeArmy()
function.
If we adjust the code to be:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
// copy the varible i into the while lexical scope
let j = i
let shooter = function () {
console.log(j);
// that should show its number
};
shooters.push(shooter); // and add it to the array
i++;
}
// ...and return the array of shooters
return shooters;
}
let army = makeArmy();
army[0](); // 0
army[1](); // 1
army[2](); // 2
The above code works as expected because instead of referencing the variable i
inside shooter()
we are copying it into j
, thus making it available to the shooter function in its own scope.
Now, my question is, why doesn't copying the i
variable inside the function itself work? even though I'm effectively still copying the variable? What am I missing here?
Code:
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function () {
let j = i;
console.log(j);
// that should show its number
};
shooters.push(shooter); // and add it to the array
i++;
}
// ...and return the array of shooters
return shooters;
}
let army = makeArmy();
// all shooters show 10 instead of their numbers 0, 1, 2, 3...
army[0](); // 10 from the shooter number 0
army[1](); // 10 from the shooter number 1
army[2](); // 10 ...and so on.
Because you are copying at a time at which i
is already 10
.
In fact, with let j = i
in the inner function, there will be no difference to using i
directly, because that assignment also executes only when you invoke that inner function, and as said, at this point i
will already be 10
, since the invocation happens long after the loop completed, in e.g. army[0]()
.
As you noted, it works if you move the let j = i
outside of the inner function (but still inside the loop). This way, the assigment to the local variable(s) j
happens on each iteration immediately, and the inner function later refers to those 10 different j
's with their right values that were previously set at that point in time when i
had the respective value that you wanted.
By the way, this problem won't exist if you'd use a for
loop instead because for
with let
has some "magic" that creates a semi-separated scope for each iteration (the exact way this works is a bit more complicated).
for (let i = 0; i < 10; i++) {
let shooter = function () {
console.log(i);
};
shooters.push(shooter);
}