en.javascript.info/1-js/06-advanced-functions/03-closure/10-make-army/solution.md
Laurent Lyaudet 3855301b7f
2 typos
2021-03-24 15:53:22 +01:00

129 lines
4.2 KiB
Markdown

Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious.
1. It creates an empty array `shooters`:
```js
let shooters = [];
```
2. Fills it with functions via `shooters.push(function)` in the loop.
Every element is a function, so the resulting array looks like this:
```js no-beautify
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); }
];
```
3. 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 (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 `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment.
Then, what will be the value of `i`?
If we look at the source:
```js
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 `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`).
As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`.
![](lexenv-makearmy-empty.svg)
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 of `i` into a variable within the `while {...}` block, like this:
```js run
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 to the current loop iteration:
![](lexenv-makearmy-while-fixed.svg)
Such a problem could also be avoided if we used `for` in the beginning, like this:
```js run demo
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, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration.
![](lexenv-makearmy-for-fixed.svg)
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.