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
   227   228   229   230   231   232   233   234   235   236   237