Почему в замыкании сохраняется первое значение переменной?
Что проверяют
Понимаете ли вы, что замыкание держит ссылку на переменную, а не копию её значения. В самой формулировке спрятана ловушка: «первое значение» это не свойство замыканий, а симптом конкретной ситуации, и интервьюер ждёт, что вы это заметите. На фронтенде за вопросом почти всегда стоит устаревшее замыкание в хуках React.
Как отвечать
Замыкание это функция вместе с лексическим окружением, в котором она создана. Значение переменной читается в момент вызова, а не в момент объявления функции, поэтому обычный счётчик и выдаёт разные числа при каждом обращении.
Значение «застывает» в трёх случаях:
- переменная передана аргументом: примитив скопирован при вызове и дальше живёт своей жизнью;
letв цикле создаёт отдельную привязку на каждую итерацию, и каждый колбэк видит свою;- колбэк создан в одном рендере, а вызывается позже: он замкнул переменные того рендера и новых не увидит.
Третий случай и есть настоящая тема вопроса:
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(id);
}, []); // count навсегда равен значению первого рендераЛечится это не добавлением count в зависимости, иначе таймер начнёт пересоздаваться на каждое изменение, а функциональным обновлением setCount(c => c + 1) либо хранением свежего значения в useRef.
Где спотыкаются
Говорят, что замыкание копирует значение. Оно держит привязку: если внешняя функция поменяет переменную уже после создания замыкания, замыкание увидит новое значение, а не то, что было при создании.
Путают вопрос с классикой про var в цикле. Там всё наоборот, сохраняется последнее значение, потому что привязка одна на весь цикл, тогда как let даёт по привязке на итерацию.
Не считают цену починки через зависимости. Переменную дописали в массив зависимостей, эффект стал пересоздавать подписку или таймер при каждом изменении, и вместо устаревшего значения в приложении появились лишние сетевые запросы и мигающий интерфейс.