This commit is contained in:
Ilya Kantor 2014-10-26 22:10:13 +03:00
parent 06f61d8ce8
commit f301cb744d
2271 changed files with 103162 additions and 0 deletions

View file

@ -0,0 +1,217 @@
# Что происходит в этом коде
Функция `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>

View file

@ -0,0 +1,31 @@
# Армия функций
[importance 5]
Следующий код создает массив функций-стрелков `shooters`. По замыслу, каждый стрелок должен выводить свой номер:
```js
//+ run
function makeArmy() {
var shooters = [];
for(var i=0; i<10; i++) {
var shooter = function() { // функция-стрелок
alert(i); // выводит свой номер
};
shooters.push(shooter);
}
return shooters;
}
var army = makeArmy();
army[0](); // стрелок выводит 10, а должен 0
army[5](); // стрелок выводит 10...
// .. все стрелки выводят 10 вместо 0,1,2...9
```
Почему все стрелки́ выводят одно и то же? Поправьте код, чтобы стрелки работали как задумано. Предложите несколько вариантов исправления.