init
This commit is contained in:
parent
06f61d8ce8
commit
f301cb744d
2271 changed files with 103162 additions and 0 deletions
|
@ -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>
|
|
@ -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
|
||||
```
|
||||
|
||||
Почему все стрелки́ выводят одно и то же? Поправьте код, чтобы стрелки работали как задумано. Предложите несколько вариантов исправления.
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue