en.javascript.info/01-js/05-functions-closures/05-closures-usage/06-make-army/solution.md
Ilya Kantor f301cb744d init
2014-10-26 22:10:13 +03:00

217 lines
No EOL
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Что происходит в этом коде
Функция `makeArmy` делает следующее:
<ol>
<li>Создаёт пустой массив `shooter`:
```js
var shooters = [];
```
</li>
<li>В цикле заполняет массив элементами через `shooter.push`.
При этом каждый элемент массива -- это функция, так что в итоге после цикла массив будет таким:
```js
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); }
];
```
Этот массив возвращается из функции.
</li>
<li>Вызов `army[5]()` -- это получение элемента массива (им будет функция), и тут же -- её запуск.</li>
</ol>
# Почему ошибка
**Вначале разберемся, почему все стрелки выводят одно и то же значение.**
В функциях-стрелках `shooter` отсутствует переменная `i`. Когда такая функция вызывается, то `i` она берет из внешнего `LexicalEnvironment`.
Чему же будет равно это значение `i`?
К моменту вызова `army[0]()`, функция `makeArmy` уже закончила работу. Цикл завершился, последнее значение было `i=10`.
В результате все функции `shooter` получают из внешнего лексического кружения это, одно и то же, последнее, значение `i=10`.
Попробуйте исправить проблему самостоятельно.
# Исправление (3 варианта)
Есть несколько способов исправить ситуацию.
<ol>
<li>**Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:**
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
*!*
var shooter = function me() {
alert( me.i );
};
shooter.i = i;
*/!*
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
В этом случае каждая функция хранит в себе свой собственный номер.
Кстати, обратите внимание на использование Named Function Expression, вот в этом участке:
```js
...
var shooter = function me() {
alert( me.i );
};
...
```
Если убрать имя `me` и оставить обращение через `shooter`, то работать не будет:
```js
for(var i=0; i<10; i++) {
var shooter = function() {
*!*
alert(shooter.i); // вывести свой номер (не работает!)
// потому что откуда функция возьмёт переменную shooter?
// ..правильно, из внешнего объекта, а там она одна на всех
*/!*
};
shooter.i = i;
shooters.push(shooter);
}
```
Вызов `alert(shooter.i)` при вызове будет искать переменную `shooter`, а эта переменная меняет значение по ходу цикла, и к моменту вызову она равна последней функции, созданной в цикле.
Если использовать Named Function Expression, то имя жёстко привязывается к конкретной функции, и поэтому в коде выше `me.i` возвращает правильный `i`.
</li>
<li>**Другое, более продвинутое решение --- использовать дополнительную функцию для того, чтобы "поймать" текущее значение `i`**:
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
*!*
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
*/!*
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит:
```js
var shooter = (function(x) {
return function() {
alert( x );
};
})(i);
```
Функция `shooter` создана как результат вызова промежуточного функционального выражения `function(x)`, которое объявляется -- и тут же выполняется, получая `x = i`.
Так как `function(x)` тут же завершается, то значение `x` больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке.
Для красоты можно изменить название переменной `x` на `i`, суть происходящего при этом не изменится:
```js
var shooter = (function(i) {
return function() {
alert( i );
};
})(i);
```
**Кстати, обратите внимание -- скобки вокруг `function(i)` не нужны**, можно и так:
```js
var shooter = function(i) { // *!*без скобок вокруг function(i)*/!*
return function() {
alert( i );
};
}(i);
```
Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что `var shooter = function`, а понял что это вызов "на месте", и присваивается его результат.
</li>
<li>**Еще один забавный способ - обернуть весь цикл во временную функцию**:
```js
//+ run
function makeArmy() {
var shooters = [];
*!*
for(var i=0; i<10; i++) (function(i) {
var shooter = function() {
alert( i );
};
shooters.push(shooter);
})(i);
*/!*
return shooters;
}
var army = makeArmy();
army[0](); // 0
army[1](); // 1
```
Вызов `(function(i) { ... })` обернут в скобки, чтобы интерпретатор понял, что это `Function Expression`.
Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание `shooter`, а просто обертываем итерацию в функцию.
</li>
</ol>