Замыкание в цикле: классическая задача с var и let
«Что выведет цикл с setTimeout?» — вопрос-ловушка, который задают почти всегда.
Проблема возникает из-за того, что
varимеет одну общую переменную на весь цикл, а коллбэки выполняются позже, когда цикл уже закончился.
Ловушка с var
Кажется, что код напечатает 0, 1, 2. На деле — три одинаковых числа.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0);
}
Вывод:
var: 3 var: 3 var: 3
Почему: var i — одна переменная на весь цикл. setTimeout откладывает коллбэки; они срабатывают, когда цикл уже завершился и i стало 3. Все три замыкания смотрят на одну и ту же i.
Решение 1: let создаёт новую переменную на итерацию
let в заголовке цикла создаёт свежую переменную для каждой итерации. Каждый коллбэк замыкается на своё значение.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("let:", i), 0);
}
Вывод:
let: 0 let: 1 let: 2
Решение 2: IIFE
До появления let проблему решали через IIFE (немедленно вызываемое функциональное выражение). Оно создаёт отдельную область видимости и «замораживает» текущее значение в параметре.
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log("iife:", j), 0);
})(i); // передаём текущее i как j
}
Вывод:
iife: 0 iife: 1 iife: 2
Что такое IIFE
IIFE — функция, которую объявляют и тут же вызывают: (function(){ ... })(). Раньше так создавали изолированную область видимости (модули, приватные переменные), пока не появились блочные let/const и модули ES.
const result = (function () {
const secret = 42; // не утекает наружу
return secret * 2;
})();
console.log(result);
Вывод:
84
Итог
- С
varцикл печатает финальное значение — одна общая переменная на все замыкания. letсоздаёт новую переменную на каждой итерации — простое решение.- IIFE — старый способ изолировать значение и создать отдельную область видимости.