217 lines
No EOL
6.9 KiB
Markdown
217 lines
No EOL
6.9 KiB
Markdown
# Что происходит в этом коде
|
||
|
||
Функция `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> |