Как исправить проблему с var в цикле с setTimeout в JavaScript
Что проверяют
Понимаете ли вы разницу между функциональной и блочной областью видимости и то, что замыкание хранит ссылку на переменную, а не её значение. Следом часто спрашивают, что изменится при задержке 0 и почему let в заголовке цикла ведёт себя особенным образом.
Как отвечать
Сначала объясните причину. var видна во всей функции (или глобально), поэтому на весь цикл существует одна переменная i. Колбэки setTimeout попадают в очередь задач и выполняются, только когда синхронный цикл уже закончился и i стала равна 5. Все пять функций читают одну и ту же переменную и выводят 5. Задержка 0 ничего не меняет: колбэк всё равно ждёт окончания текущего кода.
Способы исправить:
letв заголовке: для каждой итерации создаётся новая привязкаi, в неё копируется текущее значение. Самый короткий и современный вариант;- IIFE:
(function (j) { setTimeout(() => console.log(j), 1000); })(i), каждый вызов функции создаёт свою область. Так писали до ES2015; - аргумент таймера:
setTimeout(console.log, 1000, i). Третий и следующие аргументыsetTimeoutпередаются в колбэк, значение фиксируется в момент вызова; bind:setTimeout(console.log.bind(null, i), 1000), тоже фиксирует значение заранее;- перебор массива:
forEachсоздаёт отдельный вызов на каждый элемент.
Стоит заметить, что все пять строк появятся почти одновременно через секунду. Если нужен вывод раз в секунду, задержку считают как i * 1000.
Где спотыкаются
Говорят, что let «не поднимается». Поднимается, но до объявления находится во временной мёртвой зоне. Проблему цикла решает не это, а новая привязка на каждой итерации.
Предлагают const в классическом for. На i++ будет TypeError, const подходит только для for...of и for...in.
Пишут setTimeout(console.log(i), 1000). Функция вызывается сразу, числа выводятся мгновенно и без задержки, а таймер получает undefined вместо колбэка.