Что выведет console.log в результате выполнения цикла while?
Дан код:
var i = 10;
var array = [];
while (i--) {
(function (i) {
var i = i;
array.push(function() {
return i + i;
});
})(i);
}
console.log([
array[0](),
array[1](),
])
Что выведет console.log в результате выполнения кода?
Пошаговый разбор выполнения
Цикл while сначала вычисляет условие, и только затем выполняет тело цикла.
В условии используется i-- (постфиксный декремент): в выражение подставляется старое значение, а уменьшение переменной происходит сразу после этого.
Исходный код (для ориентира):
var i = 10;
var array = [];
while (i--) {
(function (i) {
var i = i;
array.push(function() {
return i + i;
});
})(i);
}
console.log([
array[0](),
array[1](),
])
Итерация 1
Старт: i = 10.
Условие while (i--) берёт значение 10 (истина), затем уменьшает i до 9. В тело цикла попадает уже уменьшенное значение внешней i, то есть i === 9, и именно 9 передаётся в IIFE: (function(i){...})(9).
Внутри IIFE создаётся собственная область видимости функции, а параметр i — локальная переменная этой функции.
Строка var i = i; не создаёт «вторую» переменную: в пределах одной функции var не имеет блочной области видимости, а повторное объявление допустимо; фактически остаётся одна переменная i этой функции, которой просто присваивается текущее значение i.
Далее в array кладётся функция, которая возвращает i + i, то есть 9 + 9 = 18.
Итерация 2
Перед проверкой условия: внешняя i = 9.
Условие while (i--) берёт значение 9 (истина), затем уменьшает i до 8. В IIFE передаётся 8, значит сохранённое (замкнутое) значение — 8, и array[1]() вернёт 8 + 8 = 16.
Почему значения не «уплывают»
Ключевой момент: для каждого прохода цикла IIFE вызывается немедленно и получает аргумент (текущее число), а затем внутренняя функция замыкается на переменную i этой конкретной IIFE.
Так как это разные вызовы функции, у них разные окружения (разные «ячейки памяти» для i), поэтому array[0] и array[1] возвращают разные стабильные значения.
Таблица значений по шагам
| Номер шага | Значение, которое проверяет while(i--) | Внешняя i после декремента | Аргумент IIFE (…)(i) | Результат сохранённой функции |
|---|---|---|---|---|
| 1 | 10 | 9 | 9 | 18 |
| 2 | 9 | 8 | 8 | 16 |
var i = i; выглядит как создание копии, но с var внутри одной и той же функции это не отдельная переменная, а повторное объявление той же переменной; такая запись ухудшает читаемость и часто становится источником ошибок при изучении областей видимости.Мини-схема (что где живёт)
Глобальная область:
i (внешняя)
Каждая итерация while:
1) условие использует старое i, затем i уменьшается (i--)
2) вызывается IIFE с текущим (уже уменьшенным) i
3) IIFE создаёт свою локальную i
4) внутренняя функция замыкается на локальную i IIFE
array[0] -> функция с i = 9 -> 9 + 9
array[1] -> функция с i = 8 -> 8 + 8
Теория по задаче
while (condition) и порядок вычисления
У while условие вычисляется перед каждой итерацией, и только при истинности выполняется тело цикла.
Из-за этого выражения с побочными эффектами (например, i--) прямо в условии влияют на то, какое значение окажется в теле цикла.
Постфиксный декремент x--
Постфиксная форма x-- возвращает значение до уменьшения, а уменьшение выполняется сразу после получения значения выражения.
Поэтому в while (i--) проверяется «старое» i, но внутри тела цикла i уже меньше на 1.
Пример по аналогии:
let x = 3;
let y = x--;
console.log(x, y); // x станет 2, y будет 3
var и область видимости функции
Переменные, объявленные через var, имеют область видимости функции (или глобальную, если функция отсутствует), а не область блока { ... }.
Поэтому запись var i внутри IIFE относится к одной и той же локальной переменной этой функции, даже если var i «повторяется» или стоит внутри вложенных блоков.
Также var допускает повторное объявление в той же области видимости без ошибки.
Замыкания и «запоминание» значения
Функция, помещённая в array, использует переменную i, находящуюся во внешней (для неё) функции IIFE, и тем самым образует замыкание.
Так как IIFE вызывается заново на каждой итерации, создаётся новое окружение, и каждая добавленная функция «помнит» своё значение i.
Кратко: краткая выжимка — правильный ответ [18, 16], потому что i-- в условии уменьшает внешнюю i до входа в тело цикла, а IIFE создаёт отдельное замыкание на значение i для каждой итерации.