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