Page 232 - Браун Э. - Изучаем JavaScript. Руководство по созданию современных веб-сайтов - 2017
P. 232
соnsоlе . l оg ( "Обратный отсчет : " ) ;
for ( i =5 ; i>=O ; i--) {
setTimeout ( f unction ( )
console . l og ( i===O ? "Старт ! " i ) ;
} , ( 5 - i ) * l O O O ) ;
countdown ( ) ;
Давайте сначала пройдем этот пример мысленно. Вы, вероятно, помните, что здесь
что-то не так. Все выглядит так, как будто мы выполняем обратный отсчет от 5 до О.
-
Вместо этого получаем шесть раз по 1 и без вывода строки "Старт ! " . Мы уже видели
это, когда использовали var; на сей раз мы используем let, но в объявлении за предела
ми цикла for, поэтому возникает та же проблема: цикл for быстро выполняется полно
стью, оставляя i со значением - 1, и только затем запускает на выполнение функцию
обратного вызова. Проблема в том, что, когда она выполняется, i уже имеет значение -1.
Важный урок здесь заключается в способе, которым область видимости и асин
хронное выполнение влияют друг на друга. Вызывая countdown, мы создаем замкну
тое выражение, которое содержит переменную i. Все (анонимные) обратные вызовы,
которые мы создаем в цикле for, имеют доступ к той же переменной i.
Суть этого примера в том, что в цикле for мы видим i, используемую двумя разны
ми способами. Когда мы используем ее для вычисления периода ( ( 5-i ) * 1 О О О), все рабо
тает как ожидалось: первый период - О, второй период - 1 0 00, третий период - 2 0 0 0
и т.д. Это потому, что вычисление происходит синхронно. Фактически вызов функции
setTimeout также синхронен. В ней выполняются некие вычисления, позволяющие точ
но определить момент запуска функции обратного вызова. Асинхронная часть - это
функция, которая передается функции setTimeout, и именно здесь кроется проблема.
Напомню, что мы можем решить эту проблему, используя немедленно вызывае
мое функциональное выражением (IIFE), или еще проще, переместив объявление i
в объявление цикла for.
function countdown ( ) {
соnsоlе . l оg ( " Обратный отсчет : " ) ;
for ( l et i=5 ; i>= O ; i--) { / / теперь i имеет область видимости блока
setTimeout ( f unc i on ( )
t
console . l og ( i===O ? " С тарт ! " : i ) ;
} ' ( 5 - i ) * 1 0 0 0 ) ;
countdown ( ) ;
Урок здесь состоит в том, что нужно помнить области видимости, в которых объ
являются ваши функции обратного вызова: у них будет доступ ко всему в этих об
ластях видимости (замкнутое выражение). Именно из-за этого значение переменной
может быть отличным от ожидаемого, когда функция обратного вызова выполняется
Обратные вызовы 235

