--- title: 'Объединенный асинхронный JavaScript: Таймайты и интервалы' slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals original_slug: Learn/JavaScript/Asynchronous/Таймауты_и_интервалы ---
В этом руководстве рассматриваются традиционные методы, доступные в JavaScript для асинхронного выполнения кода по истечении заданного периода времени или через регулярный интервал (например, заданное количество раз в секунду), обсуждаются их полезные свойства и рассматриваются присущие им проблемы. .
Необходимые условия: | Базовая компьютерная грамотность, достаточное понимание основ JavaScript. |
---|---|
Цель: | Понимание асинхронных циклов и интервалов, и то как их можно использовать. |
В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяли им асинхронно выполнять код по истечении определенного временного интервала и повторно выполнять асинхронный блок кода, пока вы не скажете ему остановиться.
Эти функции:
setTimeout()
setInterval()
requestAnimationFrame()
Асинхронный код, установленный этими функциями, выполняется в основном потоке (по истечении указанного им таймера).
Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval (). В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут еще больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после того, как станет доступен основной поток. (Другими словами, когда стек пуст.) Вы узнаете больше по этому вопросу по мере изучения этой статьи.
В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении. В следующих разделах мы покажем вам, как их можно использовать.
Как мы ранее отметили, setTimeout () выполняет определенный блок кода один раз по истечении заданного времени. Принимает следующие параметры:
NOTE: Указанное время (или задержка) не является гарантированным временем выполнения, а скорее минимальным временем выполнения. Обратные вызовы, которые вы передаете этим функциям, не могут выполняться, пока стек в основном потоке не станет пустым.
Как следствие, такой код, как setTimeout (fn, 0), будет выполняться, как только стек будет пуст, а не сразу. Если вы выполните такой код, как setTimeout (fn, 0), но сразу после выполнения цикла, который насчитывает от 1 до 10 миллиардов, ваш обратный вызов будет выполнен через несколько секунд.
В следующем примере, браузер будет ожидать две секунды перед тем как выполнит анонимную функцию, тогда отобразит сообщение (живой пример, и исходный код):
let myGreeting = setTimeout(function() { alert('Hello, Mr. Universe!'); }, 2000)
Указанные вами функции не обязательно должны быть анонимными. Вы можете дать своей функции имя и даже определить ее где-нибудь еще и передать ссылку на функцию в setTimeout (). Следующие две версии фрагмента кода эквивалентны первой:
// С именованной функцией let myGreeting = setTimeout(function sayHi() { alert('Hello, Mr. Universe!'); }, 2000) // С функцией определенной отдельно function sayHi() { alert('Hello Mr. Universe!'); } let myGreeting = setTimeout(sayHi, 2000);
Это может быть полезно, если у вас есть функция, которую нужно вызывать как по таймауту, так например и в ответ на событие. Но это также может помочь поддерживать ваш код в чистоте, особенно если обратный вызов тайм-аута занимает больше, чем несколько строк кода.
setTimeout ()
возвращает значение идентификатора, которое можно использовать для ссылки на тайм-аут позже, например, когда вы хотите его остановить.
Любые параметры, которые вы хотите передать функции, выполняемой внутри setTimeout (), должны быть переданы ей как дополнительные параметры в конце списка.
Например, вы можете реорганизовать предыдущую функцию, чтобы она передавала привет любому имени, переданному ей:
function sayHi(who) { alert(`Hello ${who}!`); }
Теперь вы можете передать имя в вызов setTimeout () в качестве третьего параметра:
let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');
Наконец, если был создан тайм-аут, вы можете отменить его до истечения указанного времени, вызвав clearTimeout()
, передав ему идентификатор вызова setTimeout()
в качестве параметра. Итак, чтобы отменить указанный выше тайм-аут, вы должны сделать следующее:
clearTimeout(myGreeting);
Note: См.greeter-app.html
для более полной демонстрации, которая позволяет вам указать имя для приветствия и отменить приветствие с помощью отдельной кнопки (см. исходный код).
setTimeout ()
отлично работает, когда вам нужно один раз запустить код по истечении заданного периода времени. Но что происходит, когда вам нужно запускать код снова и снова - например, в случае анимации?
Здесь пригодится setInterval() . Работает очень похоже на setTimeout (), за исключением того, что функция, которую вы передаете в качестве первого параметра, выполняется повторно не менее чем за количество миллисекунд, заданных вторым параметром. Вы также можете передать любые параметры, необходимые для выполняемой функции, в качестве последующих параметров вызова setInterval ().
Давайте посмотрим на пример. Следующая функция создает новый объект Date()
, с помощью toLocaleTimeString()
извлекает из него строку с временем и отображает ее в пользовательском интерфейсе. Затем он запускает функцию один раз в секунду с помощью setInterval()
, создавая эффект цифровых часов, которые обновляются раз в секунду ( реальный пример, и исходный код):
function displayTime() { let date = new Date(); let time = date.toLocaleTimeString(); document.getElementById('demo').textContent = time; } const createClock = setInterval(displayTime, 1000);
Как и setTimeout ()
, setInterval ()
возвращает определенное значение, которое вы можете использовать позже, когда вам нужно очистить интервал.
setInterval ()
выполняет задачу постоянно. setInterval () продолжает выполнять задачу вечно, если вы что-то с ней не сделаете. Возможно, вам понадобится способ остановить такие задачи, иначе вы можете получить ошибки, если браузер не сможет выполнить какие-либо другие версии задачи или если анимация, обрабатываемая задачей, завершилась. Вы можете сделать это так же, как останавливаете timeouts
- передавая идентификатор, возвращаемый вызовом setInterval ()
, в функцию clearInterval ()
:
const myInterval = setInterval(myFunction, 2000); clearInterval(myInterval);
Учитывая все вышесказанное, у нас есть для вас задача. Возьмите копию нашего примера setInterval-clock.html
, и измените ее так, чтобы создать свой собственный простой секундомер.
Вам нужно отображать время, как и раньше, но в этом примере вам нужно:
0
.Несколько подсказок для вас:
3600
секунд.10
, чтобы они больше походили на традиционные часы.0
, очистить интервал, а затем немедленно обновить отображение.setInterval ()
к часам, что приведет к неправильному поведению.Note: Если вы застряли, вы можете увидеть нашу версию (см. также исходный код ).
setTimeout ()
и setInterval ()
При работе с setTimeout ()
и setInterval ()
следует помнить о нескольких вещах. Давайте рассмотрим их.
Есть еще один способ использования setTimeout ()
: вы можете вызвать его рекурсивно для повторного запуска одного и того же кода вместо использования setInterval ()
.
В приведенном ниже примере используется рекурсивный setTimeout () для запуска переданной функции каждые 100 миллисекунд:
let i = 1; setTimeout(function run() { console.log(i); i++; setTimeout(run, 100); }, 100);
Сравните приведенный выше пример со следующим - здесь используется setInterval ()
для достижения того же эффекта:
let i = 1; setInterval(function run() { console.log(i); i++ }, 100);
setTimeout ()
отличается от setInterval ()
?Разница между двумя версиями приведенного выше кода невелика.
setTimeout ()
гарантирует такую же задержку между выполнениями. (Например, 100 мс в приведенном выше случае.) Код будет запущен, затем подождет 100 миллисекунд, прежде чем запустится снова, поэтому интервал будет одинаковым, независимо от того, сколько времени требуется для выполнения кода.setInterval ()
работает несколько иначе. Выбранный вами интервал включает время, затрачиваемое на выполнение кода, который вы хотите запустить. Предположим, что выполнение кода занимает 40
миллисекунд - тогда интервал составляет всего 60
миллисекунд.setTimeout ()
каждая итерация может вычислять различную задержку перед запуском следующей итерации. Другими словами, значение второго параметра может указывать другое время в миллисекундах для ожидания перед повторным запуском кода.Когда ваш код потенциально может занять больше времени, чем назначенный вами интервал времени, лучше использовать рекурсивный setTimeout ()
- это сохранит постоянный временной интервал между выполнениями независимо от того, сколько времени потребуется для выполнения кода, и вы избежите ошибок.
Использование 0 в качестве значения для setTimeout ()
позволяет планировать выполнение указанной функции обратного вызова как можно скорее, но только после того, как будет запущен основной поток кода.
Например, код приведенный ниже (рабочий код) выводит alert содержащий "Hello"
, затем alert содержащий "World"
как только вы нажмете ОК в первом alert.
setTimeout(function() { alert('World'); }, 0); alert('Hello');
Это может быть полезно в тех случаях, когда вы хотите установить блок кода для запуска, как только весь основной поток завершит работу - поместите его в цикл событий async, чтобы он запускался сразу после этого.
clearTimeout()
или clearInterval()
clearTimeout () и clearInterval ()
используют один и тот же список записей для очистки. Интересно, что это означает, что вы можете использовать любой метод для очистки setTimeout () или setInterval ().
Для согласованности следует использовать clearTimeout ()
для очистки записей setTimeout ()
и clearInterval ()
для очистки записей setInterval ()
. Это поможет избежать путаницы.
requestAnimationFrame()
это специализированная функция цикла, созданная для эффективного запуска анимации в браузере. По сути, это современная версия setInterval ()
- она выполняет указанный блок кода до того, как браузер перерисовывает изображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.
Он был создан в ответ на проблемы с setInterval ()
, который, например, не работает с частотой кадров, оптимизированной для устройства, иногда пропускает кадры, продолжает работать, даже если вкладка не является активной вкладкой или анимация прокручивается со страницы и т. д.(Читай об этом больше в CreativeJS.)
Note: Вы можете найти примеры использования requestAnimationFrame()
в этом курсе — например в Рисование графики, and Практика построения объектов.
Метод принимает в качестве аргумента обратный вызов, который должен быть вызван перед перерисовкой. Это общий шаблон, в котором он используется:
function draw() { // Drawing code goes here requestAnimationFrame(draw); } draw();
Идея состоит в том, чтобы определить функцию, в которой ваша анимация обновляется (например, ваши спрайты перемещаются, счет обновляется, данные обновляются или что-то еще). Затем вы вызываете его, чтобы начать процесс. В конце функционального блока вы вызываете requestAnimationFrame ()
со ссылкой на функцию, переданной в качестве параметра, и это дает браузеру указание вызвать функцию снова при следующей перерисовке дисплея. Затем он выполняется непрерывно, поскольку код рекурсивно вызывает requestAnimationFrame ().
Note: Если вы хотите выполнить простое постоянное анимирование DOM , CSS Анимация вероятно будет быстрее. Она высисляется непосредственно внутренним кодом браузера, а не JavaScript.
Однако, если вы делаете что-то более сложное, включающее объекты, которые не доступны напрямую в the DOM (такие как 2D Canvas API или WebGL ), requestAnimationFrame()
предпочтительный вариант в большинстве случаев.
Плавность анимации напрямую зависит от частоты кадров анимации и измеряется в кадрах в секунду (fps). Чем выше это число, тем плавнее будет выглядеть ваша анимация до точки.
Поскольку большинство экранов имеют частоту обновления 60 Гц, максимальная частота кадров, к которой вы можете стремиться, составляет 60 кадров в секунду (FPS) при работе с веб-браузерами. Однако большее количество кадров означает больше обработки, которая часто может вызывать заикание и пропуски, также известные как пропадание кадров или заедание.
Если у вас есть монитор с частотой обновления 60 Гц и вы хотите достичь 60 кадров в секунду, у вас есть около 16,7 миллисекунд (1000/60)
для выполнения кода анимации для рендеринга каждого кадра. Это напоминание о том, что вам нужно помнить об объеме кода, который вы пытаетесь запустить во время каждого прохождения цикла анимации.
requestAnimationFrame ()
всегда пытается приблизиться к этому волшебному значению 60 FPS, насколько это возможно. Иногда это невозможно - если у вас действительно сложная анимация и вы запускаете ее на медленном компьютере, частота кадров будет меньше. Во всех случаях requestAnimationFrame ()
всегда будет делать все возможное с тем, что у него есть.
Давайте поговорим еще немного о том, чем метод requestAnimationFrame ()
отличается от других методов, используемых ранее. Глядя на наш код сверху:
function draw() { // Drawing code goes here requestAnimationFrame(draw); } draw();
Такой же код с использованием setInterval()
:
function draw() { // Drawing code goes here } setInterval(draw, 17);
Как мы уже говорили ранее, вы не указываете временной интервал для requestAnimationFrame ()
. Просто он работает максимально быстро и плавно в текущих условиях. Браузер также не тратит время на запуск, если по какой-то причине анимация выходит за пределы экрана и т. д.
setInterval ()
, с другой стороны, требует указания интервала. Мы пришли к нашему окончательному значению 17 по формуле 1000 миллисекунд / 60 Гц, а затем округлили его в большую сторону. Округление - хорошая идея; если вы округлите в меньшую сторону, браузер может попытаться запустить анимацию со скоростью, превышающей 60 кадров в секунду, и в любом случае это не повлияет на плавность анимации. Как мы уже говорили, стандартная частота обновления - 60 Гц.
Фактическому обратному вызову, переданному в функцию requestAnimationFrame ()
, также может быть задан параметр: значение отметки времени, которое представляет время с момента начала работы requestAnimationFrame ().
Это полезно, поскольку позволяет запускать вещи в определенное время и в постоянном темпе, независимо от того, насколько быстрым или медленным может быть ваше устройство. Общий шаблон, который вы бы использовали, выглядит примерно так:
let startTime = null; function draw(timestamp) { if (!startTime) { startTime = timestamp; } currentTime = timestamp - startTime; // Do something based on current time requestAnimationFrame(draw); } draw();
requestAnimationFrame ()
поддерживается в более поздних версиях браузеров, чем setInterval ()
/ setTimeout ()
. Интересно, что он доступен в Internet Explorer 10 и выше.
Итак, если вам не тербуется поддержка старых версий IE, нет особых причин не использовать requestAnimationFrame()
.
Хватит теории! Давайте выполним упражнение с использованием requestAnimationFrame()
. Создадим простую анимацию "spinner animation"—вы могли ее видеть в приложениях когда происходят задержки при ответе с сервера и т.п..
Note: Для такой простой анимации, вам следовало бы использовать CSS . Однако такой вид анимации очень полезен для демонстрации requestAnimationFrame()
, вы скорее всего будете использовать этот метод когда делаете что-то более сложное, например обновление отображения игры в каждом кадре.
Возьмите базовый HTML шаблон (такой как этот).
Поместите пустой {{htmlelement("div")}} елемент внутри элемента {{htmlelement("body")}}, затем добавьте внутрь символ ↻ . Этот символ будет действовать как spinner в нашем примере.
Применитеpply следующий CSS к HTML шаблону (любым предпочитаемым способом). Он установ красный фон на странице, высоту <body>
равную 100%
высоты {{htmlelement("html")}} , и центрирует <div>
внутри <body>
, по горизонтали и вертикали.
html { background-color: white; height: 100%; } body { height: inherit; background-color: red; margin: 0; display: flex; justify-content: center; align-items: center; } div { display: inline-block; font-size: 10rem; }
Разместите {{htmlelement("script")}} элемент перед </body>
.
Разместите следующий JavaScript код в <script>
. Здесь вы сохраняете ссылку на <div>
внутри, устанавливаете дяпеременной rotateCount
значение 0
, устанавливаете неинициализированную переменную, которая позже будет использоваться для хранения ссылки на вызов requestAnimationFrame()
, и устанавливаете для переменной startTime
значение null
, которая будет позже использоваться для хранения времени начала requestAnimationFrame()
.
const spinner = document.querySelector('div'); let rotateCount = 0; let startTime = null; let rAF;
Под предыдущим кодом вставьте функцию draw()
соторая будет использоваться для хранения нашешо кода анимации, который включает параметр timestamp
:
function draw(timestamp) { }
Внутри draw ()
добавьте следующие строки. Они определят время начала, если оно еще не определено (это произойдет только на первой итерации цикла), и установят для параметра rotateCount
значение для поворота счетчика (текущая временная метка, возьмите начальную временную метку, разделенную на три, чтобы замедлиться):
if (!startTime) { startTime = timestamp; } rotateCount = (timestamp - startTime) / 3;
Под предыдущей строкой внутри draw ()
добавьте следующий блок - он проверяет, превышает ли значение rotateCount 359
(например, 360
, полный круг). Если это так, он устанавливает значение по модулю 360
(то есть остаток, оставшийся после деления значения на 360
), поэтому круговая анимация может продолжаться непрерывно с разумным низким значением. Обратите внимание, что это не является строго необходимым, но легче работать со значениями от 0 до 359
градусов, чем со значениями типа «128000
градусов».
if (rotateCount > 359) { rotateCount %= 360; }
spinner.style.transform = `rotate(${rotateCount}deg)`;
В самом низу внутри функции draw () вставьте следующую строку. Это ключ ко всей операции - вы устанавливаете для переменной, определенной ранее, активный вызов requestAnimation (), который принимает функцию draw () в качестве своего параметра. Это запускает анимацию, постоянно выполняя функцию draw () со скоростью, близкой к 60 FPS.
rAF = requestAnimationFrame(draw);
Ниже, вызовите функцию draw()
для запуска анимации.
draw();
Note: Вы можете посмотреть рабочий образец на GitHub. ( исходный код.)
Очистить вызов requestAnimationFrame ()
можно, вызвав соответствующий метод cancelAnimationFrame ()
. (Обратите внимание, что имя функции начинается с «cancel», а не «clear», как у методов «set ...».)
Просто передайте ему значение, возвращаемое вызовом requestAnimationFrame () для отмены, которое вы сохранили в переменной rAF:
cancelAnimationFrame(rAF);
В этом упражнении мы хотели бы, чтобы вы протестировали метод cancelAnimationFrame ()
, взяв наш предыдущий пример и обновив его, добавив прослушиватель событий для запуска и остановки счетчика при щелчке мышью в любом месте страницы.
Подсказки:
<body>
. Имеет смысл поместить его в элемент <body>
, если вы хотите максимизировать интерактивную область - событие всплывает до его дочерних элементов.Note: Для начала попробуйте сами; если вы действительно застряли, посмотрите наш живой пример и исходный код.
requestAnimationFrame()
Одним из ограничений requestAnimationFrame ()
является то, что вы не можете выбирать частоту кадров. В большинстве случаев это не проблема, так как обычно вы хотите, чтобы ваша анимация работала как можно плавнее. Но как насчет того, чтобы создать олдскульную 8-битную анимацию?
Это было проблемой, например в анимации ходьбы, вдохновленной островом обезьян, из статьи Drawing Graphics:
{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}
В этом примере вы должны анимировать как положение персонажа на экране, так и отображаемый спрайт. В анимации спрайта всего 6 кадров. Если бы вы показывали разные кадры спрайта для каждого кадра, отображаемого на экране, с помощью requestAnimationFrame (), Guybrush двигал бы конечностями слишком быстро, и анимация выглядела бы нелепо. Следовательно, в этом примере регулируется скорость, с которой спрайт циклически повторяет свои кадры, используя следующий код:
if (posX % 13 === 0) { if (sprite === 5) { sprite = 0; } else { sprite++; } }
Таким образом, код циклически повторяет спрайт только один раз каждые 13 кадров анимации.
... Фактически, это примерно каждые 6,5 кадров, поскольку мы обновляем posX (положение персонажа на экране) на два кадра:
if (posX > width/2) { newStartPos = -( (width/2) + 102 ); posX = Math.ceil(newStartPos / 13) * 13; console.log(posX); } else { posX += 2; }
Это код, который вычисляет, как обновлять позицию в каждом кадре анимации.
Метод, который вы используете для регулирования анимации, будет зависеть от вашего конкретного кода. Например, в предыдущем примере счетчика вы могли заставить его двигаться медленнее, увеличивая rotateCount только на единицу в каждом кадре вместо двух.
В последнем разделе этой статьи вы создадите игру на реакцию для двух игроков. В игре будет два игрока, один из которых управляет игрой с помощью клавиши A, а другой - с помощью клавиши L.
При нажатии кнопки «Start» счетчик, подобный тому, что мы видели ранее, отображается в течение случайного промежутка времени от 5 до 10 секунд. По истечении этого времени появится сообщение «PLAYERS GO !!» - как только это произойдет, первый игрок, который нажмет свою кнопку управления, выиграет игру.
{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}
Давайте поработаем над этим:
Прежде всего, скачайте стартовый файл. Он содержит законченную структуру HTML и стили CSS, что дает нам игровую доску, которая показывает информацию двух игроков (как показано выше), но с счетчиком и параграфом результатов, отображаемыми друг над другом. Вам нужно просто написать JavaScript код.
Внутри пустого элемента {{htmlelement("script")}} на вашей старнице, начните с добавления следующих строк кода, котороые определяют некотороые переменные и константы, которые вам понадобятся в дальнейшем:
const spinner = document.querySelector('.spinner p'); const spinnerContainer = document.querySelector('.spinner'); let rotateCount = 0; let startTime = null; let rAF; const btn = document.querySelector('button'); const result = document.querySelector('.result');
В следующем порядке:
Ниже добавьте следующую функцию. Она просто берет два числа и возвращает случайное число между ними. Это понадобится вам позже, чтобы сгенерировать случайный интервал ожидания.
function random(min,max) { var num = Math.floor(Math.random()*(max-min)) + min; return num; }
Затем добавьте функцию draw(), которая анимирует спиннер. Это очень похоже на версию из предыдущего примера простого счетчика:
function draw(timestamp) { if(!startTime) { startTime = timestamp; } rotateCount = (timestamp - startTime) / 3; if(rotateCount > 359) { rotateCount %= 360; } spinner.style.transform = 'rotate(' + rotateCount + 'deg)'; rAF = requestAnimationFrame(draw); }
Теперь пришло время настроить начальное состояние приложения при первой загрузке страницы. Добавьте следующие две строки, которые просто скрывают абзац результатов и контейнер счетчика с помощью display: none
;.
result.style.display = 'none'; spinnerContainer.style.display = 'none';
Затем определите функцию reset ()
, которая возвращает приложение в исходное состояние, необходимое для повторного запуска игры после ее завершения. Добавьте в конец кода следующее:
function reset() { btn.style.display = 'block'; result.textContent = ''; result.style.display = 'none'; }
Хорошо, хватит подготовки! Пришло время сделать игру доступной! Добавьте в свой код следующий блок. Функция start ()
вызывает draw ()
, чтобы запустить вращение спиннера и отобразить его в пользовательском интерфейсе, скрыть кнопку Start, чтобы вы не могли испортить игру, запустив ее несколько раз одновременно, и запускает вызов setTimeout ()
, который выполняется функция setEndgame ()
по прошествии случайного интервала от 5 до 10 секунд. Следующий блок также добавляет прослушиватель событий к вашей кнопке для запуска функции start ()
при ее нажатии.
btn.addEventListener('click', start); function start() { draw(); spinnerContainer.style.display = 'block'; btn.style.display = 'none'; setTimeout(setEndgame, random(5000,10000)); }
Note: Вы увидете, что этот пример вызывает setTimeout()
без сохранения возвращаемого значения. (не let myTimeout = setTimeout(functionName, interval)
.)
Это прекрасно работает, если вам не нужно очищать интервал / тайм-аут в любой момент. Если вы это сделаете, вам нужно будет сохранить возвращенный идентификатор!
Конечным результатом предыдущего кода является то, что при нажатии кнопки «Start» отображается спиннер, и игроки вынуждены ждать произвольное количество времени, прежде чем их попросят нажать их кнопку. Эта последняя часть обрабатывается функцией setEndgame ()
, которую вы определите позже.
Добавьте в свой код следующую функцию:
function setEndgame() { cancelAnimationFrame(rAF); spinnerContainer.style.display = 'none'; result.style.display = 'block'; result.textContent = 'PLAYERS GO!!'; document.addEventListener('keydown', keyHandler); function keyHandler(e) { let isOver = false; console.log(e.key); if (e.key === "a") { result.textContent = 'Player 1 won!!'; isOver = true; } else if (e.key === "l") { result.textContent = 'Player 2 won!!'; isOver = true; } if (isOver) { document.removeEventListener('keydown', keyHandler); setTimeout(reset, 5000); } }; }
Выполните следующие инструкции:
keydown
. При нажатии любой кнопки запускается функция keyHandler()
.keyHandler()
, код включает обьект события в качестве параметра (представленного e
) — его свойство {{domxref("KeyboardEvent.key", "key")}} содержит только что нажатую клавишу, и вы можете использовать это для твета на определенные нажатия клавиш определенными действиями.isOver
значение false, чтобы мы могли отслеживать, были ли нажаты правильные клавиши, чтобы игрок 1 или 2 выиграл. Мы не хотим, чтобы игра заканчивалась при нажатии неправильной клваиши.e.key
в консоли, это полезный способ узнать значение различных клавиш, которые вы нажимаете.e.key
принимает значение "a", отобразить сообщение о том, что Player 1 выиграл, а когда e.key
это "l", отобразить сообщение о том, что Player 2 выиграл. (Note: Это будет работать только со строчными буквами a и l — если переданы прописные A или L , это считается другими клавишами!) Если была нажата одна из этих клавиш, установите для isOver
значение true
.isOver
равно true
, удалите прослушиватель событий keydown
с помощью {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} чтобы после того, как произошло выигрышное нажатие, больше не было возможности ввода с клавиатуры, чтобы испортить финальный результат игры. Вы также используете setTimeout()
для вызова reset()
через 5 секунд — как обьяснялось ранее, эта функция сбрасывает игру обратно в исходное состояние, чтобы можно было начать новую игру.Вот и все - вы справились!
Note: Если вы где то застряли, взгляните на наша версия игры (см. также исходный код ).
Вот и все — все основы асинхронных циклов и интервалов рассмотрены в статье. Вы найдете эти методы полезными во многих ситуациях, но постарайтесь не злоупотреблять ими! Поскольку они по-прежнему выполняются в основном потоке, тяжелые и интенсивные обратные вызовы (особенно те, которые управляют DOM) могут действительно замедлить страницу, если вы не будете осторожныl.
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}