Army of functions
The following code creates an array of shooters.
Every function is meant to output its number. But something is wrong…
function makeArmy() {
let shooters = [];
let i = 0;
while (i < 10) {
let shooter = function() { // create a shooter function,
alert( 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.
Why do all of the shooters show the same value?
Fix the code so that they work as intended.
Let’s examine what exactly happens inside makeArmy, and the solution will become obvious.
-
It creates an empty array
shooters:let shooters = []; -
Fills it with functions via
shooters.push(function)in the loop.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 elementarmy[5]from the array (which is a function) and calls it.Now why do all such functions show the same value,
10?That’s because there’s no local variable
iinsideshooterfunctions. When such a function is called, it takesifrom its outer lexical environment.Then, 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 }; shooters.push(shooter); // add function to the array i++; } ... }We can see that all
shooterfunctions are created in the lexical environment ofmakeArmy()function. But whenarmy[5]()is called,makeArmyhas already finished its job, and the final value ofiis10(whilestops ati=10).As the result, all
shooterfunctions 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 this, we can copy the value ofiinto a variable within thewhile {...}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](); // 5Here
let j = ideclares an “iteration-local” variablejand copiesiinto it. Primitives are copied “by value”, so we actually get an independent copy ofi, belonging to the current loop iteration.The shooters work correctly, because the value of
inow lives a little bit closer. Not inmakeArmy()Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration:Such a problem could also be avoided if we used
forin 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](); // 5That’s essentially the same, because
foron each iteration generates a new lexical environment, with its own variablei. Soshootergenerated in every iteration references its owni, 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, 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.