Замыкание и i-- в while: что выведет console.log
Дан код:
let i = 10, arr = [];
while (i--) {
arr.push(() => i + i);
}
console.log([arr[0](), arr[0]()]);
Что выведет console.log?
Разбор кода по шагам
Исходный код:
let i = 10, arr = [];
while (i--) {
arr.push(() => i + i);
}
console.log([arr[0](), arr[0]()]);
x-- возвращает старое значение выражения, а уменьшение переменной выполняется сразу после вычисления этого выражения.Что делает условие while
Условие while (i--) проверяет “истинность” по значению i до уменьшения, а само уменьшение i выполняется после вычисления условия и до выполнения тела цикла.
Трассировка (ключевые моменты):
| Момент | Значение i перед проверкой | Результат выражения i-- | Входит в цикл? | Значение i внутри тела |
|---|---|---|---|---|
| Старт | 10 | 10 | да | 9 |
| ... | ... | ... | ... | ... |
| Последний заход | 1 | 1 | да | 0 |
| Проверка после цикла | 0 | 0 | нет | (тело не выполняется) |
Важно: когда i становится 0, выражение i-- возвращает 0 (это “ложь”), но затем всё равно уменьшает i до -1, даже несмотря на то, что тело цикла уже не выполняется.
Итог после завершения while: i равно -1.
Что именно кладётся в arr
В arr добавляются функции-замыкания: каждая стрелочная функция сохраняет доступ к лексическому окружению, в котором она была создана (то есть к переменным внешней области видимости).
Ключевая идея: замыкание в JavaScript — это не “снимок” значения переменной, а доступ к самой переменной через лексическое окружение; если переменная меняется, то при вызове функции будет использовано актуальное на момент вызова значение.
Схема (упрощённо):
[внешнее окружение]
i ---> (одна и та же переменная, меняется в while)
arr -> [ f0, f1, f2, ... ]
|
+--> f0: () => i + i (читает i при вызове)
let само по себе не гарантирует “новую i на каждой итерации” в любом цикле: если переменная объявлена один раз снаружи, а затем изменяется, то все замыкания будут ссылаться на одну и ту же переменную.Почему результат именно [-2, -2]
- После окончания цикла
iравно -1 (это следует из трассировки условияwhile (i--)). arr[0]— это функция() => i + i, которая при вызове читает текущее значениеiиз внешней области видимости.- Поэтому при каждом вызове
arr[0]()вычисляется-1 + -1, то есть -2.
Следовательно, console.log([arr[0](), arr[0]()]); выводит [-2, -2].
Как получить другие результаты (для понимания)
Вариант A: “зафиксировать” значение в отдельной переменной
Идея: внутри тела цикла создать новую константу, равную текущему i, и замыкать уже её (то есть “снимок” значения).
let i = 10, arr = [];
while (i--) {
const snapshot = i;
arr.push(() => snapshot + snapshot);
}
console.log([arr[0](), arr[0]()]);
В этом случае первая функция будет возвращать 18, потому что на первой итерации внутри тела i уже стало 9, значит snapshot равно 9, и 9 + 9 = 18.
Вариант B: Отличие от `for (let i = ...)`
При создании замыканий в циклах типичная ошибка связана с тем, что несколько функций могут разделять одно и то же лексическое окружение с изменяемой переменной, из‑за чего при вызове они видят “последнее” значение.
В конструкции for (let i = ...) часто ожидается другое поведение, потому что переменная i связана с механизмом итераций цикла for; это нередко приводит к тому, что замыкания получают значения “по итерациям”, а не одно общее изменяемое значение. Для while с внешней переменной, изменяемой декрементом, такого эффекта по умолчанию нет.
Кратко: в while (i--) условие использует старое значение i, затем уменьшает i, после завершения цикла i становится -1; все функции в arr читают одну и ту же переменную i при вызове, поэтому arr[0]() возвращает -2, а итоговый вывод — [-2, -2].