Skip to content

Commit a47629f

Browse files
authored
Обновление решения задачи "Армия функций"
1 parent ca05f23 commit a47629f

File tree

1 file changed

+87
-81
lines changed
  • 1-js/06-advanced-functions/03-closure/10-make-army

1 file changed

+87
-81
lines changed

1-js/06-advanced-functions/03-closure/10-make-army/solution.md

Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -27,96 +27,102 @@
2727

2828
3. Функция возвращает массив.
2929

30-
Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её.
30+
Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её.
3131

32-
Теперь, почему все эти функции показывают одно и то же?
32+
Теперь, почему все эти функции показывают одно и то же?
3333

34-
Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения.
34+
Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения.
3535

36-
Какое будет значение у `i`?
36+
Какое будет значение у `i`?
3737

38-
Если мы посмотрим в исходный код:
38+
Если мы посмотрим в исходный код:
3939

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-
Можно использовать другой трюк, давайте рассмотрим его для лучшего понимания предмета:
40+
```js
41+
function makeArmy() {
42+
...
43+
let i = 0;
44+
while (i < 10) {
45+
let shooter = function() { // функция shooter
46+
alert( i ); // должна выводить порядковый номер
47+
};
48+
shooters.push(shooter); // и добавлять стрелка в массив
49+
i++;
50+
}
51+
...
52+
}
53+
```
9454

95-
```js run
96-
function makeArmy() {
97-
let shooters = [];
55+
...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: `10` (конец цикла `while`).
56+
57+
Как результат, все функции `shooter` получат одно и то же значение из внешнего лексического окружения: последнее значение `i=10`.
58+
59+
![](lexenv-makearmy-empty.svg)
60+
61+
Как вы можете видеть выше, на каждой итерации блока `while {...}` создается новое лексическое окружение. Чтобы исправить это, мы можем скопировать значение `i` в переменную внутри блока `while {...}`, например, так:
62+
63+
```js run
64+
function makeArmy() {
65+
let shooters = [];
66+
67+
let i = 0;
68+
while (i < 10) {
69+
*!*
70+
let j = i;
71+
*/!*
72+
let shooter = function() { // функция shooter
73+
alert( *!*j*/!* ); // должна выводить порядковый номер
74+
};
75+
shooters.push(shooter);
76+
i++;
77+
}
78+
79+
return shooters;
80+
}
81+
82+
let army = makeArmy();
83+
84+
// теперь код работает правильно
85+
army[0](); // 0
86+
army[5](); // 5
87+
```
9888

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-
}
89+
Здесь `let j = i` объявляет "итерационно-локальную" переменную `j` и копирует в нее `i`. Примитивы копируются "по значению", поэтому фактически мы получаем независимую копию `i`, принадлежащую текущей итерации цикла.
90+
91+
Функции `shooter` работают правильно, потому что значение `i` теперь живет чуть ближе. Не в лексическом окружении `makeArmy()`, а в лексическом окружении, соответствующем текущей итерации цикла:
92+
93+
![](lexenv-makearmy-while-fixed.svg)
94+
95+
Этой проблемы также можно было бы избежать, если бы мы использовали `for` в начале, например, так:
96+
97+
```js run demo
98+
function makeArmy() {
99+
100+
let shooters = [];
101+
102+
*!*
103+
for (let i = 0; i < 10; i++) {
104+
*/!*
105+
let shooter = function() { // функция shooter
106+
alert( i ); // должна выводить порядковый номер
107+
};
108+
shooters.push(shooter);
109+
}
110+
111+
return shooters;
112+
}
113+
114+
let army = makeArmy();
115+
116+
army[0](); // 0
117+
army[5](); // 5
118+
```
110119

111-
return shooters;
112-
}
120+
По сути, это то же самое, поскольку `for` на каждой итерации создает новое лексическое окружение со своей переменной `i`. Поэтому функция `shooter`, создаваемая на каждой итерации, ссылается на свою собственную переменную `i`, причем именно с этой итерации.
113121

114-
let army = makeArmy();
122+
![](lexenv-makearmy-for-fixed.svg)
115123

116-
army[0](); // 0
117-
army[5](); // 5
118-
```
124+
Теперь, когда вы приложили столько усилий, чтобы прочитать это объяснение, а конечный вариант оказался так прост - использовать `for`, вы можете задаться вопросом -- стоило ли оно того?
119125

120-
Цикл `while` так же, как и `for`, создаёт новое лексическое окружение для каждой итерации. Так что тут мы хотим убедиться, что он получит правильное значение для `shooter`.
126+
Что ж, если бы вы могли легко ответить на вопрос из задачи, вы бы не стали читать решение. Так что, должно быть, эта задача помогла вам лучше понять суть дела.
121127

122-
Мы копируем `let j = i`. Это создаёт локальную для итерации переменную `j` и копирует в неё `i`. Примитивы копируются "по значению", поэтому мы получаем совершенно независимую копию `i`, принадлежащую текущей итерации цикла.
128+
Кроме того, действительно встречаются случаи, когда человек предпочитает `while`, а не `for`, и другие сценарии, где такие проблемы реальны.

0 commit comments

Comments
 (0)