Skip to content

Commit 280e233

Browse files
authored
8-make-army => 10-make-army
1 parent 4662bad commit 280e233

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function makeArmy() {
2+
3+
let shooters = [];
4+
5+
for(let i = 0; i < 10; i++) {
6+
let shooter = function() { // shooter function
7+
alert( i ); // should show its number
8+
};
9+
shooters.push(shooter);
10+
}
11+
12+
return shooters;
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function makeArmy() {
2+
let shooters = [];
3+
4+
let i = 0;
5+
while (i < 10) {
6+
let shooter = function() { // shooter function
7+
alert( i ); // should show its number
8+
};
9+
shooters.push(shooter);
10+
i++;
11+
}
12+
13+
return shooters;
14+
}
15+
16+
/*
17+
let army = makeArmy();
18+
19+
army[0](); // the shooter number 0 shows 10
20+
army[5](); // and number 5 also outputs 10...
21+
// ... all shooters show 10 instead of their 0, 1, 2, 3...
22+
*/
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
describe("army", function() {
2+
3+
let army;
4+
5+
before(function() {
6+
army = makeArmy();
7+
window.alert = sinon.stub(window, "alert");
8+
});
9+
10+
it("army[0] shows 0", function() {
11+
army[0]();
12+
assert(alert.calledWith(0));
13+
});
14+
15+
16+
it("army[5] shows 5", function() {
17+
army[5]();
18+
assert(alert.calledWith(5));
19+
});
20+
21+
after(function() {
22+
window.alert.restore();
23+
});
24+
25+
});

1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
2+
Давайте посмотрим, что происходит внутри `makeArmy`, и решение станет очевидным.
3+
4+
1. Она создаёт пустой массив `shooters`:
5+
6+
```js
7+
let shooters = [];
8+
```
9+
2. В цикле заполняет его `shooters.push(function...)`.
10+
11+
Каждый элемент -- это функция, так что получится такой массив:
12+
13+
```js no-beautify
14+
shooters = [
15+
function () { alert(i); },
16+
function () { alert(i); },
17+
function () { alert(i); },
18+
function () { alert(i); },
19+
function () { alert(i); },
20+
function () { alert(i); },
21+
function () { alert(i); },
22+
function () { alert(i); },
23+
function () { alert(i); },
24+
function () { alert(i); }
25+
];
26+
```
27+
28+
3. Функция возвращает массив.
29+
30+
Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её.
31+
32+
Теперь, почему все эти функции показывают одно и то же?
33+
34+
Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения.
35+
36+
Какое будет значение у `i`?
37+
38+
Если мы посмотрим в исходный код:
39+
40+
```js
41+
function makeArmy() {
42+
...
43+
let i = 0;
44+
while (i < 10) {
45+
let shooter = function() { // функция shooter
46+
alert( i ); // должна выводить порядковый номер
47+
};
48+
...
49+
}
50+
...
51+
}
52+
```
53+
54+
...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: 10 (конец цикла `while`).
55+
56+
Как результат, все функции `shooter` получат одно и то же из внешнего окружения: последнее значение `i=10`.
57+
58+
Мы можем это исправить, переместив определение переменной в цикл:
59+
60+
```js run demo
61+
function makeArmy() {
62+
63+
let shooters = [];
64+
65+
*!*
66+
for(let i = 0; i < 10; i++) {
67+
*/!*
68+
let shooter = function() { // функция shooter
69+
alert( i ); // должна выводить порядковый номер
70+
};
71+
shooters.push(shooter);
72+
73+
74+
}
75+
76+
return shooters;
77+
}
78+
79+
let army = makeArmy();
80+
81+
army[0](); // 0
82+
army[5](); // 5
83+
```
84+
85+
Теперь она работает правильно, потому что каждый раз, когда выполняется блок кода `for (let i=0...) {...}`, для него создаётся новое лексическое окружение с соответствующей переменной `i`.
86+
87+
Так что значение `i` теперь живёт немного ближе. Не в лексическом окружении `makeArmy()`, а в лексическом окружении, которое соответствует текущей итерации цикла. Вот почему теперь она работает.
88+
89+
![](lexenv-makearmy.svg)
90+
91+
Здесь мы переписали `while` в `for`.
92+
93+
Можно использовать другой трюк, давайте рассмотрим его для лучшего понимания предмета:
94+
95+
```js run
96+
function makeArmy() {
97+
let shooters = [];
98+
99+
let i = 0;
100+
while (i < 10) {
101+
*!*
102+
let j = i;
103+
*/!*
104+
let shooter = function() { // функция shooter
105+
alert( *!*j*/!* ); // должна выводить порядковый номер
106+
};
107+
shooters.push(shooter);
108+
i++;
109+
}
110+
111+
return shooters;
112+
}
113+
114+
let army = makeArmy();
115+
116+
army[0](); // 0
117+
army[5](); // 5
118+
```
119+
120+
Цикл `while` так же, как и `for`, создаёт новое лексическое окружение для каждой итерации. Так что тут мы хотим убедиться, что он получит правильное значение для `shooter`.
121+
122+
Мы копируем `let j = i`. Это создаёт локальную для итерации переменную `j` и копирует в неё `i`. Примитивы копируются "по значению", поэтому мы получаем совершенно независимую копию `i`, принадлежащую текущей итерации цикла.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
importance: 5
2+
3+
---
4+
5+
# Армия функций
6+
7+
Следующий код создаёт массив из стрелков (`shooters`).
8+
9+
Каждая функция предназначена выводить их порядковые номера. Но что-то пошло не так...
10+
11+
```js run
12+
function makeArmy() {
13+
let shooters = [];
14+
15+
let i = 0;
16+
while (i < 10) {
17+
let shooter = function() { // функция shooter
18+
alert( i ); // должна выводить порядковый номер
19+
};
20+
shooters.push(shooter);
21+
i++;
22+
}
23+
24+
return shooters;
25+
}
26+
27+
let army = makeArmy();
28+
29+
army[0](); // у 0-го стрелка будет номер 10
30+
army[5](); // и у 5-го стрелка тоже будет номер 10
31+
// ... у всех стрелков будет номер 10, вместо 0, 1, 2, 3...
32+
```
33+
34+
Почему у всех стрелков одинаковые номера? Почините код, чтобы он работал как задумано.
35+

0 commit comments

Comments
 (0)