|
27 | 27 |
|
28 | 28 | 3. Функция возвращает массив. |
29 | 29 |
|
30 | | -Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её. |
| 30 | + Позже вызов `army[5]()` получит элемент `army[5]` из массива (это будет функция) и вызовет её. |
31 | 31 |
|
32 | | -Теперь, почему все эти функции показывают одно и то же? |
| 32 | + Теперь, почему все эти функции показывают одно и то же? |
33 | 33 |
|
34 | | -Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения. |
| 34 | + Всё потому, что внутри функций `shooter` нет локальной переменной `i`. Когда вызывается такая функция, она берёт `i` из своего внешнего лексического окружения. |
35 | 35 |
|
36 | | -Какое будет значение у `i`? |
| 36 | + Какое будет значение у `i`? |
37 | 37 |
|
38 | | -Если мы посмотрим в исходный код: |
| 38 | + Если мы посмотрим в исходный код: |
39 | 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 | | - |
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 | + ``` |
94 | 54 |
|
95 | | -```js run |
96 | | -function makeArmy() { |
97 | | - let shooters = []; |
| 55 | + ...Мы увидим, что оно живёт в лексическом окружении, связанном с текущим вызовом `makeArmy()`. Но, когда вызывается `army[5]()`, `makeArmy` уже завершила свою работу, и последнее значение `i`: `10` (конец цикла `while`). |
| 56 | + |
| 57 | + Как результат, все функции `shooter` получат одно и то же значение из внешнего лексического окружения: последнее значение `i=10`. |
| 58 | + |
| 59 | +  |
| 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 | + ``` |
98 | 88 |
|
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 | +  |
| 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 | + ``` |
110 | 119 |
|
111 | | - return shooters; |
112 | | -} |
| 120 | + По сути, это то же самое, поскольку `for` на каждой итерации создает новое лексическое окружение со своей переменной `i`. Поэтому функция `shooter`, создаваемая на каждой итерации, ссылается на свою собственную переменную `i`, причем именно с этой итерации. |
113 | 121 |
|
114 | | -let army = makeArmy(); |
| 122 | +  |
115 | 123 |
|
116 | | -army[0](); // 0 |
117 | | -army[5](); // 5 |
118 | | -``` |
| 124 | +Теперь, когда вы приложили столько усилий, чтобы прочитать это объяснение, а конечный вариант оказался так прост - использовать `for`, вы можете задаться вопросом -- стоило ли оно того? |
119 | 125 |
|
120 | | -Цикл `while` так же, как и `for`, создаёт новое лексическое окружение для каждой итерации. Так что тут мы хотим убедиться, что он получит правильное значение для `shooter`. |
| 126 | +Что ж, если бы вы могли легко ответить на вопрос из задачи, вы бы не стали читать решение. Так что, должно быть, эта задача помогла вам лучше понять суть дела. |
121 | 127 |
|
122 | | -Мы копируем `let j = i`. Это создаёт локальную для итерации переменную `j` и копирует в неё `i`. Примитивы копируются "по значению", поэтому мы получаем совершенно независимую копию `i`, принадлежащую текущей итерации цикла. |
| 128 | +Кроме того, действительно встречаются случаи, когда человек предпочитает `while`, а не `for`, и другие сценарии, где такие проблемы реальны. |
0 commit comments