Замыкания в цикле
В прошлом уроке к каждой карточке-функции была прикреплена записка — переменная. Записка не копия: функция держит саму переменную. Поэтому функция видит то значение, которое лежит в переменной в момент вызова функции:
let n = 1;
const show = () => n;
n = 2;
console.log(show()); // 2Давайте разберём по строкам:
let n = 1создаёт переменную со значением1.const show = () => nсоздаёт функцию, которая читаетn. Значение1она себе не копирует.n = 2меняет ту самую переменную.show()читаетnв момент вызова и возвращает2.
Это важно, когда функции создают в цикле, а вызывают после него. Тогда всё решает вопрос: сколько записок было в цикле — одна на всех или своя на каждом круге?
let в заголовке for
Цикл ниже складывает в массив три стрелочные функции. Их вызывают уже после цикла, и каждая возвращает своё число:
const fns = [];
for (let i = 0; i < 3; i++) {
fns.push(() => i * 10);
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 10 20Переменная, объявленная через let в заголовке for, создаётся заново на каждой итерации цикла. Каждый круг идёт так:
- Создаётся новая
iи получает значение прошлой. - Со второго круга к новой
iсначала применяетсяi++, а потом условиеi < 3проверяет её. - Стрелочная функция, созданная на этом круге, замыкает эту
i.
Эту i больше никто не меняет: следующий круг работает уже со своей переменной. Каждая карточка уносит свою записку. Поэтому первая функция возвращает 0, вторая 10, третья 20.
Здесь аналогия неточна: новая записка не пустая. На неё сразу переписывают значение с записки прошлого круга.
Цикл for…of с const устроен так же: у каждой итерации своя переменная.
Одна var на цикл
Слово var создаёт переменную с областью видимости функции и не замечает блоков. Такая i одна на весь цикл, а не своя на каждую итерацию. Все функции, созданные в цикле, замыкают одну и ту же переменную: одна записка прикреплена сразу ко всем карточкам.
Значение они читают при вызове, когда цикл уже закончился. Если вызвать функцию сразу, на той же итерации, разницы между let и var не видно.
После цикла в i лежит значение, на котором условие стало ложным. В цикле for (var i = 0; i < 3; i++) это 3, а не 2. Последние шаги цикла идут так:
- Последняя итерация работает с
i, равной2. i++увеличиваетiдо3.- Проверка
i < 3даётfalseи останавливает цикл.
Этот вопрос часто задают на собеседовании: функции создают в цикле с var и спрашивают, что они вернут после цикла. Ответ: все вернут одно и то же, последнее значение i, а с let каждая вернёт своё. Хороший ответ называет и причину: var даёт одну переменную на весь цикл, а функции читают её уже после цикла.
Частая ошибка
В цикле с функциями написали var вместо let:
const fns = [];
for (var i = 0; i < 3; i++) { // ✗
fns.push(() => i * 10);
}
console.log(fns[0](), fns[1](), fns[2]()); // 30 30 30Ошибки нет, но все три функции возвращают 30. Они читают одну общую i, а при вызове в ней уже 3. Замените var на let: тогда каждая итерация получит свою i, и программа напечатает 0 10 20.
Главное
- Замыкание читает значение переменной в момент вызова, а не в момент создания функции.
letв заголовкеforсоздаёт новую переменную на каждой итерации, и у каждой функции из цикла свояi.varдаёт одну переменную на весь цикл, поэтому функции, вызванные после цикла, видят её последнее значение.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.