Содержание

Справочник javascript: setTimeout

Этот метод выполняет код(или функцию), указанный в первом аргументе, асинхронно, с задержкой в delay миллисекунд.

В отличие от метода setInterval, setTimeout выполняет код только один раз.

В каком виде указывать первый параметр — в виде строки кода или функции — разницы нет.

Следующие два вызова работают одинаково:

// первый аргумент - строка
setTimeout('alert("прошла секунда")', 1000)
// первый аргумент - функция
function second_passed() {
  alert("прошла секунда")
}
setTimeout(second_passed, 1000)

При указании строки кода — интерпретатор динамически создает анонимную функцию с телом из данной строки. Но более правильным считается объявление функции в явном виде, например так:

setTimeout(function() { alert('0.5 секунды') }, 500)

Вызов со строкой существует для совместимости с прежними версиями javascript.

Контекст выполнения,

this

Функция выполняется в другом контексте, отличном от контекста, в котором задается

setTimeout.

При этом значение this = window, поэтому о передаче правильного this надо позаботиться отдельно.

Пример: Без передачи this

object = { 
   func: function() { alert(this) }
}

setTimeout( object.func , 1000) // this будет равно window

Можно указать this явно, используя промежуточную функцию.

Пример: Передача this через call

object = { 
   func: function() { alert(this) }
}

// правильный вариант с передачей this
setTimeout( function() { object.func() } , 1000)

Как правило, this передаетcя через замыкание. Для этого используется промежуточная переменная во внешней функции, которой присваивается

this:

...
var self = this // промежуточная перменная
function fun() {
   alert(self)
}
setTimeout(fun, 1000)

Отмена выполнения

Вы можете отменить выполнение setTimeout при помощи clearTimeout, используя для этого идентификатор таймаута.

var timeout_id = setTimeout(...)
clearTimeout(timeout_id)

Пример: Рабочий пример

<input type="button" value="Запустить таймаут"/>
<input type="button" value="Остановить отсчет"/>
<script>
function go() { alert('Я сработало') }

function on() {
    timeoutId = setTimeout(go, 3000)
}

function off() {
    clearTimeout(timeoutId)
}
</script>

Минимальная задержка

Минимально возможная задержка в разных браузерах варьируется. В среднем на момент написания этого текста (январь 2011) она составляет 12 мс. То есть, разницы между

setTimeout(.., 1) и setTimeout(.., 12), как правило, нет.

Производительность

Большое количество таймеров может привести к серьезной нагрузке на процессор.

Это в первую очередь касается приложений, в которых одновременно анимируется большое количество объектов. В этом случае, по возможности, следует использовать один таймер, который выполняет всю анимацию, а не множество независимых.

Объединённый асинхронный JavaScript: Таймауты и интервалы — Изучение веб-разработки

В этом руководстве рассматриваются традиционные методы, доступные в JavaScript для асинхронного выполнения кода по истечении заданного периода времени или через регулярный интервал (например, заданное количество раз в секунду), обсуждаются их полезные свойства и рассматриваются присущие им проблемы. .

Необходимые условия: Базовая компьютерная грамотность, достаточное понимание основ JavaScript.
Цель: Понимание асинхронных циклов и интервалов, и то как их можно использовать.

В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяли им асинхронно выполнять код по истечении определённого временного интервала и повторно выполнять асинхронный блок кода, пока вы не скажете ему остановиться.

Эти функции:

setTimeout()
Выполняет указанный блок кода один раз по истечении указанного времени
setInterval()
Выполняет указанный блок кода несколько раз с определённым интервалом между каждым вызовом.
requestAnimationFrame()
Современная версия setInterval (). Выполняют указанный блок кода перед тем, как браузер в следующий раз перерисовывает отображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.

Асинхронный код, установленный этими функциями, выполняется в основном потоке (по истечении указанного им таймера).

Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval (). В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут ещё больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после того, как станет доступен основной поток. (Другими словами, когда стек пуст.) вы узнаете больше по этому вопросу по мере изучения этой статьи.

В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении. В следующих разделах мы покажем вам, как их можно использовать.

Как мы ранее отметили, setTimeout () выполняет определённый блок кода один раз по истечении заданного времени. Принимает следующие параметры:

  • Функция для запуска или ссылка на функцию, определённую в другом месте.
  • Число, представляющее интервал времени в миллисекундах (1000 миллисекунд равняется 1 секунде) ожидания перед выполнением кода. Если вы укажете значение 0 (или просто опустите значение), функция запустится как можно скорее. (См. Примечание ниже о том, почему он запускается «как можно скорее», а не «сразу».) Подробнее о том, почему вы, возможно, захотите сделать это позже.
  • Значений, представляющие любые параметры, которые вы хотите передать функции при её запуске.

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 ()

Любые параметры, которые вы хотите передать функции, выполняемой внутри 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 , и измените её так, чтобы создать свой собственный простой секундомер.

Вам нужно отображать время, как и раньше, но в этом примере вам нужно:

  • Кнопка «Start» для запуска секундомера.
  • Кнопка «Stop» для паузы/остановки.
  • Кнопка «Reset», чтобы сбросить счётчик времени на 0.
  • Дисплей времени, чтобы отображать количество прошедших секунд а не фактическое время.

Несколько подсказок для вас:

  • Вы можете структурировать и стилизовать разметку кнопок по своему усмотрению; просто убедитесь, что вы используете семантический HTML с кавычками, которые позволяют захватывать ссылки на кнопки с помощью JavaScript.
  • Вероятно, вы захотите создать переменную, которая начинается с 0, а затем увеличивается на единицу каждую секунду с использованием постоянного цикла.
  • Этот пример проще создать без использования объекта Date (), как мы это делали в нашей версии, но он будет менее точен — вы не можете гарантировать, что колбэк сработает ровно через 1000 мс. Более точным способом было бы запустить startTime = Date.now (), чтобы получить метку времени, когда пользователь нажал кнопку запуска, а затем выполнить Date.now () — startTime, чтобы получить количество миллисекунд после того, как была нажата кнопка запуска .
  • Вам также нужно рассчитать количество часов, минут и секунд как отдельные значения, а затем отображать их вместе в строке после каждой итерации цикла. На втором счётчике вы можете отработать каждую из них.
  • Как вы могли бы их рассчитать? Подумайте об этом:
    • В одном часе 3600 секунд.
    • Количество минут — это количество секунд, оставшееся после вычитания всех часов, разделённое на 60.
    • Количество секунд будет количеством секунд, оставшихся после вычитания всех минут.
  • Вам необходимо включить начальный ноль в отображаемые значения, если сумма меньше 10, чтобы они больше походили на традиционные часы.
  • Чтобы приостановить секундомер, вам нужно очистить интервал. Чтобы сбросить его, вам нудно установить счётчик обратно на 0, очистить интервал, а затем немедленно обновить отображение.
  • Вероятно, вам следует отключить кнопку запуска после её нажатия один раз и снова включить её после того, как вы остановили / сбросили её. В противном случае многократное нажатие кнопки запуска приведёт к применению нескольких 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.)

Метод принимает в качестве аргумента колбэк, который должен быть вызван перед перерисовкой. Это общий шаблон, в котором он используется:

function draw() {
   
   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() от setInterval() and setTimeout()?

Давайте поговорим ещё немного о том, чем метод requestAnimationFrame () отличается от других методов, используемых ранее. Глядя на наш код сверху:

function draw() {
   
   requestAnimationFrame(draw);
}

draw();

Такой же код с использованием setInterval():

function draw() {
   
}

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;

   

   requestAnimationFrame(draw);
}

draw();

Поддержка браузерами

requestAnimationFrame () поддерживается в более поздних версиях браузеров, чем setInterval () / setTimeout (). Интересно, что он доступен в Internet Explorer 10 и выше.

Итак, если вам не требуется поддержка старых версий IE, нет особых причин не использовать requestAnimationFrame().

Простой пример

Хватит теории! Давайте выполним упражнение с использованием requestAnimationFrame() . Создадим простую анимацию «spinner animation»—вы могли её видеть в приложениях когда происходят задержки при ответе с сервера и т.п..

Note: Для такой простой анимации, вам следовало бы использовать CSS . Однако такой вид анимации очень полезен для демонстрации requestAnimationFrame() , вы скорее всего будете использовать этот метод когда делаете что-то более сложное, например обновление отображения игры в каждом кадре.

  1. Возьмите базовый HTML шаблон (такой как этот).

  2. Поместите пустой  <div> элемент внутри элемента <body>, затем добавьте внутрь символ ↻ . Этот символ будет действовать как spinner в нашем примере.

  3. Примените следующий CSS к HTML шаблону (любым предпочитаемым способом). Он установ красный фон на странице, высоту <body> равную 100% высоты <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;
    }
  4. Разместите  <script> элемент перед </body> .

  5. Разместите следующий JavaScript код в  <script> . Здесь вы сохраняете ссылку на <div> внутри, устанавливаете для переменной rotateCount значение 0, устанавливаете неинициализированную переменную, которая позже будет использоваться для хранения ссылки на вызов requestAnimationFrame(), и устанавливаете для переменной startTime значение null, которая будет позже использоваться для хранения времени начала requestAnimationFrame().

    const spinner = document.querySelector('div');
    let rotateCount = 0;
    let startTime = null;
    let rAF;
    
  6. Под предыдущим кодом вставьте функцию draw() которая будет использоваться для хранения нашего кода анимации, который включает параметр timestamp :

    function draw(timestamp) {
    
    }
  7. Внутри draw () добавьте следующие строки. Они определят время начала, если оно ещё не определено (это произойдёт только на первой итерации цикла), и установят для параметра rotateCount значение для поворота счётчика (текущая временная метка, возьмите начальную временную метку, разделённую на три, чтобы замедлиться):

      if (!startTime) {
       startTime = timestamp;
      }
    
      rotateCount = (timestamp - startTime) / 3;
    
  8. Под предыдущей строкой внутри draw () добавьте следующий блок — он проверяет, превышает ли значение rotateCount 359 (например, 360, полный круг). Если это так, он устанавливает значение по модулю 360 (то есть остаток, оставшийся после деления значения на 360), поэтому круговая анимация может продолжаться непрерывно с разумным низким значением. Обратите внимание, что это не является строго необходимым, но легче работать со значениями от 0 до 359 градусов, чем со значениями типа «128000 градусов».

    if (rotateCount > 359) {
      rotateCount %= 360;
    }
  9. Затем, под предыдущим блоком, добавьте следующую строку, чтобы вращать spinner:
    spinner.style.transform = `rotate(${rotateCount}deg)`;
  10. В самом низу внутри функции draw () вставьте следующую строку. Это ключ ко всей операции — вы устанавливаете для переменной, определённой ранее, активный вызов requestAnimation (), который принимает функцию draw () в качестве своего параметра. Это запускает анимацию, постоянно выполняя функцию draw () со скоростью, близкой к 60 FPS.

    rAF = requestAnimationFrame(draw);
  11. Ниже, вызовите функцию draw() для запуска анимации.

Очистка вызова  requestAnimationFrame() 

Очистить вызов requestAnimationFrame () можно, вызвав соответствующий метод cancelAnimationFrame (). (Обратите внимание, что имя функции начинается с «cancel», а не «clear», как у методов «set …».)

Просто передайте ему значение, возвращаемое вызовом requestAnimationFrame () для отмены, которое вы сохранили в переменной rAF:

cancelAnimationFrame(rAF);

Активное обучение: запуск и остановка нашей анимации

В этом упражнении мы хотели бы, чтобы вы протестировали метод cancelAnimationFrame (), взяв наш предыдущий пример и обновив его, добавив обработчик событий для запуска и остановки счётчика при щелчке мышью в любом месте страницы.

Подсказки:

  • Обработчик события щелчка можно добавить к большинству элементов, включая документ <body>. Имеет смысл поместить его в элемент <body>, если вы хотите максимизировать интерактивную область — событие всплывает до его дочерних элементов.
  • Вы захотите добавить переменную отслеживания, чтобы проверить, вращается ли счётчик или нет, очистив кадр анимации, если он есть, и снова вызвать его, если это не так.

Регулировка анимации

requestAnimationFrame() 

Одним из ограничений requestAnimationFrame () является то, что вы не можете выбирать частоту кадров. В большинстве случаев это не проблема, так как обычно вы хотите, чтобы ваша анимация работала как можно плавное. Но как насчёт того, чтобы создать олдскульную 8-битную анимацию?

Это было проблемой, например в анимации ходьбы, вдохновлённой островом обезьян, из статьи Drawing Graphics:

В этом примере вы должны анимировать как положение персонажа на экране, так и отображаемый спрайт. В анимации спрайта всего 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 !!» — как только это произойдёт, первый игрок, который нажмёт свою кнопку управления, выиграет игру.

Давайте поработаем над этим:

  1. Прежде всего, скачайте стартовый файл. Он содержит законченную структуру HTML и стили CSS, что даёт нам игровую доску, которая показывает информацию двух игроков (как показано выше), но с счётчиком и параграфом результатов, отображаемыми друг над другом. Вам нужно просто написать JavaScript код.

  2. Внутри пустого элемента <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');

    В следующем порядке:

    1. Ссылка на спиннер, чтобы вы могли его анимировать.
    2. Ссылка на элемент <div> содержащий спиннер, используемый для отображения и скрытия.
    3. Счётчик поворотов. Он определяет, на сколько вы хотите показывать вращение спиннера на каждом кадре анимации.
    4. Нулевое время начала. Это будет заполнено временем начала, когда счётчик начнёт вращаться.
    5. Неинициализированная переменная для последующего хранения вызова requestAnimationFrame() который анимирует спиннер.
    6. Ссылка на кнопку Start .
    7. Ссылка на параграф результатов.
  3. Ниже добавьте следующую функцию. Она просто берёт два числа и возвращает случайное число между ними. Это понадобится вам позже, чтобы сгенерировать случайный интервал ожидания.

    function random(min,max) {
      var num = Math.floor(Math.random()*(max-min)) + min;
      return num;
    }
  4. Затем добавьте функцию 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);
    }
  5. Теперь пришло время настроить начальное состояние приложения при первой загрузке страницы. Добавьте следующие две строки, которые просто скрывают абзац результатов и контейнер счётчика с помощью display: none ;.

    result.style.display = 'none';
    spinnerContainer.style.display = 'none';
  6. Затем определите функцию reset (), которая возвращает приложение в исходное состояние, необходимое для повторного запуска игры после её завершения. Добавьте в конец кода следующее:

    function reset() {
      btn.style.display = 'block';
      result.textContent = '';
      result.style.display = 'none';
    }
  7. Хорошо, хватит подготовки! Пришло время сделать игру доступной! Добавьте в свой код следующий блок. Функция 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 (), которую вы определите позже.

  8. Добавьте в свой код следующую функцию:

    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);
        }
      };
    }

    Выполните следующие инструкции:

    1. Во-первых, отмените анимацию спиннера с помощью cancelAnimationFrame() (всегда полезно очистить ненужные процессы), и скройте контейнер счётчика.
    2. Затем, отобразите абзац с результатами и установите для его текстового содержимого значение «PLAYERS GO!!»  чтобы сообщить игрокам, что теперь они могут нажать свою кнопку, чтобы победить.
    3. Прикрепите к документу обработчик событий keydown . При нажатии любой кнопки запускается функция keyHandler().
    4. Внутри keyHandler(), код включает объект события в качестве параметра (представленного e) — его свойство key содержит только что нажатую клавишу, и вы можете использовать это для ответа на определённые нажатия клавиш определёнными действиями.
    5. Установите для переменной isOver значение false, чтобы мы могли отслеживать, были ли нажаты правильные клавиши, чтобы игрок 1 или 2 выиграл. Мы не хотим, чтобы игра заканчивалась при нажатии неправильной клавиши.
    6. Регистрация e.key в консоли, это полезный способ узнать значение различных клавиш, которые вы нажимаете.
    7. Когда e.key принимает значение «a», отобразить сообщение о том, что Player 1 выиграл, а когда e.key это «l», отобразить сообщение о том, что Player 2 выиграл. (Note: Это будет работать только со строчными буквами a и l — если переданы прописные A или L , это считается другими клавишами!) Если была нажата одна из этих клавиш, установите для isOver значение true.
    8. Только если isOver равно true, удалите обработчик событий keydown с помощью removeEventListener() чтобы после того, как произошло выигрышное нажатие, больше не было возможности ввода с клавиатуры, чтобы испортить финальный результат игры. Вы также используете setTimeout() для вызова reset() через 5 секунд — как объяснялось ранее, эта функция сбрасывает игру обратно в исходное состояние, чтобы можно было начать новую игру.

Вот и все — вы справились!

Вот и все — все основы асинхронных циклов и интервалов рассмотрены в статье. Вы найдёте эти методы полезными во многих ситуациях, но постарайтесь не злоупотреблять ими! Поскольку они по-прежнему выполняются в основном потоке, тяжёлые и интенсивные колбэки (особенно те, которые управляют DOM) могут действительно замедлить страницу, если вы не будете осторожны.

Как в JavaScript работает `setTimeout` и что происходит если он установлен на 0?

14 февраля 2020 г.

Чтобы разобраться в том как работает setTimeout и функции которые вызываются в нем, нужно разобраться в том как вообще работает JavaScript. Разобравшись в этом, вы станете куда увереннее пользоваться джаваскриптом.

Простейший пример setTimeout:

setTimeout(function() {

console.log('какой-то обратный вызов');

}, 0);

setTimeout(коллбек, задержка) означает «вызови обратный вызов (коллбек, это может быть какая либо функция, в нашем случае это функция с console.log("какой-то обратный вызов")) после заданной задержки». Но что происходит если мы задаем задержку в 0? Получается, он должен вызвать коллбек сразу же? Может показаться что да, но все сложнее.

Подумайте, что сделает этот код (к слову, подобные вопросы любят задавать на интервью):

setTimeout(function() {

console.log('setTimeout заданный в 0');

}, 0);

console.log('раз');

console.log('два');

console.log('три');

В какой последовательности будут отображены эти сообщения?

Отобразит он вот это:

раз

два

три

setTimeout заданный в 0

Почему-то JavaScript прописывает раз, два, три, и только после этого прописывает setTimeout заданный в 0.

Можно пойти дальше, и, скажем, написать такой код с несколькими циклами:

setTimeout(function() {

console.log('setTimeout заданный в 0');

}, 0);

for (i = 0; i < 1000; i++) {

console.log('раз');

}

for (i = 0; i < 1000; i++) {

console.log('два');

}

for (i = 0; i < 1000; i++) {

console.log('три');

}

Но в результате setTimeout заданный в 0 все равно окажется в конце:

тысяча сообщений раз

тысяча сообщений два

тысяча сообщений три

setTimeout заданный в 0

Дело не в задержке самой по себе, хоть мы и задали нулевую задержку, проблема в том когда js запускает функцию внутри setTimeout, а делает он это не в тот же самый момент когда запускается код, хотя, «интуитивно» кажется что должно же быть именно так.

Как setTimeout работает в JavaScript

Важно понимать что JavaScript это однопоточный (single-threaded) язык программирования. Про это часто пишут и говорят, но зачем-то эту концепцию излишне усложняют, хотя, вообще-то, все очень просто:

JavaScript все делает в одном потоке. Он последовательно берет задачи из специального списка, который называется Call Stack. Проще всего воспринимать этот список как TODO лист, задачи из которого JavaScript и выполняет.

Именно по таким правилам живут эти console.log:

console.log('раз');

console.log('два');

console.log('три');

JavaScript получает задачу написать в консоли «раз», эта задача появляется вверху списка, он выполняет ее и убирает ее из списка.

Все становиться сложнее когда появляются асинхронные операции, к которым, собственно, и относиться setTimeout.

А сейчас, страшная тайна, меня это в свое время удивило, но вообще-то setTimeout это не часть JavaScript движка! Это Web API, браузеры (и, скажем, nodejs), просто, по доброте душевной, позволяют нам использовать setTimeout, именно браузеры обрабатывают и запускают указанную в setTimeout задержку. JavaScript работает c этим API, но это просто часть его окружения, это не сам движок.

Так как же будет обрабатываться наш код? Когда мы вызываем setTimeout:

setTimeout(function() {

console.log('setTimeout заданный в 0');

}, 0);

Сообщение добавляется в очередь с указанным обратным вызовом. А остальная часть кода:

console.log('раз');

console.log('два');

console.log('три');

Продолжает выполняться синхронно. Как только этот код полностью завершится, цикл обработки событий (event loop — эвент луп) начнет опрашивать очередь сообщений на наличие следующего сообщения. Он найдет сообщение с обратным вызовом (callback) setTimeout, после чего начнется его обработка (выполнение обратного вызова).

Так что же все таки происходит, если задержка установлена ​​на 0? Новое сообщение будет немедленно добавлено в очередь, но обработано оно будет только после того как текущий исполняемый код будет завершен и все ранее добавленные сообщения будут обработаны.

Проще говоря, обратный вызов выполняется только после завершения текущего выполняемого кода.

Получается, затронув такой, как казалось, простой вопрос, почему setTimeout с задержкой заданной в 0 не исполняется сразу же, нам пришлось иметь дело и с обратными вызовами, и с внешним окружением (Web API), и с эвент лупом, и тд.

Дальнейшее изучение

Изначально эта статья была переводом вопроса и ответа со stackoverflow: What is setTimeout doing when set to 0 milliseconds?, но через какое-то время все переросло в отдельную статью. Но частично это все еще перевод этого вопроса и ответа.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

Крутое видео на YouTube про то как работает эвент луп: What the heck is the event loop anyway? | Philip Roberts | JSConf EU

Визуализация того как работает JavaScript: http://latentflip.com/loupe

Еще один визуализатор для JavaScript, но визуализирующий контексты, замыкания и области видимости: https://tylermcginnis.com/javascript-visualizer/

Иван Ганев

веб разработчик

Прочее | Javascript с примерами кода

Поскольку JavaScript поддерживает асинхронность, есть возможность запланировать выполнение функции, используя функции setTimeout и setInterval.

Замечание: Таймауты не являются частью стандарта ECMAScript, они были разработаны как раздел спецификации DOM.

function foo() {}
var id = setTimeout(foo, 1000) // возвращает число > 0

Функция setTimeout возвращает идентификатор таймаута и планирует вызвать foo через, примерно, тысячу миллисекунд. Функция foo при этом будет вызвана ровно один раз.

В зависимости от разрешения таймера в используемом для запуска кода движке JavaScript, а также с учётом того, что JavaScript является однопоточным языком и посторонний код может заблокировать выполнение потока, нет никакой гарантии, что переданный код будет выполнен ровно через указанное в вызове setTimeout время.

Переданная первым параметром функция будет вызвана как глобальный объект — это значит, что оператор this в вызываемой функции будет ссылаться на этот самый объект.

function Foo() {
  this.value = 42
  this.method = function () {
    // this ссылается на глобальный объект
    console.log(this.value) // выведет в лог undefined
  }
  setTimeout(this.method, 500)
}
new Foo()

Замечание: Поскольку setTimeout принимает объект функции в качестве первого параметра, часто совершается ошибка в использовании setTimeout(foo(), 1000), при котором будет использоваться возвращённое значение от вызова функции foo, а не вызываться сама функция foo. В большинстве случаев ошибка пройдёт незамеченной, а в случае если функция возвращает undefined, setTimeout вообще не породит никакой ошибки.

Поочерёдные вызовы с использованием

setInterval

setTimeout вызывает функцию единожды; setInterval — как и предполагает название — вызывает функцию каждые X миллисекунд. И его использование не рекомендуется.

В то время, когда исполняющийся код будет блокироваться во время вызова с таймаутом, setInterval будет продолжать планировать последующие вызовы переданной функции. Это может (особенно в случае небольших интервалов) повлечь за собой выстраивание вызовов функций в очередь.

function foo() {
  // что-то, что выполняется одну секунду
}
setInterval(foo, 100)

В приведённом коде foo выполнится один раз и заблокирует этим главный поток на одну секунду.

Пока foo блокирует код, setInterval продолжает планировать последующие её вызовы. Теперь, когда первая foo закончила выполнение, в очереди будут уже десять ожидающих выполнения вызовов foo.

Разбираемся с потенциальной блокировкой кода

Самый простой и контролируемый способ — использовать setTimeout внутри самой функции.

function foo() {
  // что-то, выполняющееся одну секунду
  setTimeout(foo, 100)
}
foo()

Такой способ не только инкапсулирует вызов setTimeout, но и предотвращает от очередей блокирующих вызовов и при этом обеспечивает дополнительный контроль. Сама функция foo теперь принимает решение, хочет ли она запускаться ещё раз или нет.

Очистка таймаутов вручную

Удаление таймаутов и интервалов работает через передачу соответствующего идентификатора либо в функцию clearTimeout, либо в функцию clearInterval — в зависимости от того, какая функция set... использовалась для его получения.

var id = setTimeout(foo, 1000)
clearTimeout(id)

Очистка всех таймаутов

Из-за того, что встроенного метода для удаления всех таймаутов и/или интервалов не существует, для достижения этой цели приходится использовать брутфорс.

// удаляем "все" таймауты
for (var i = 1; i < 1000; i++) {
  clearTimeout(i)
}

Вполне могут остаться таймауты, которые не будут захвачены этим произвольным числом; так что всё же рекомендуется следить за идентификаторами всех создающихся таймаутов, за счёт чего их можно будет удалять индивидуально.

Скрытое использование

eval

setTimeout и setInterval могут принимать строку в качестве первого параметра. Эту возможность не следует использовать никогда, поскольку изнутри при этом производится скрытый вызов eval.

Замечание: Поскольку функции работы с таймаутами не определены в стандарте ECMAScript, точная внутренняя механика их работы может различаться от движка к движку. Известно, что Microsoft JScript использует конструктор Function вместо eval.

function foo() {
  // будет вызвана
}

function bar() {
  function foo() {
    // никогда не будет вызывана
  }
  setTimeout('foo()', 1000)
}
bar()

Поскольку eval в этом случае не вызывается напрямую, переданная в setTimeout строка будет выполнена в глобальной области видимости; так что локальная переменная foo из области видимости bar не будет выполнена.

По этим же причинам рекомендуется не использовать строку для передачи аргументов в функцию, которая должна быть вызвана из одной из двух функций, работающих с таймаутами.

function foo(a, b, c) {}

// НИКОГДА не делайте такого
setTimeout('foo(1,2, 3)', 1000)

// Вместо этого используйте анонимную функцию
setTimeout(function () {
  foo(1, 2, 3)
}, 1000)

Замечание: При том, что синтаксис setTimeout(foo, 1000, 1, 2, 3) разрешено использовать, это крайне не рекомендуется, поскольку может привести к сложно распознаваемым ошибкам при работе с методами.

Заключение

Никогда не используйте строки как параметры setTimeout или setInterval. Это явный признак действительно плохого кода. Если вызываемой функции необходимо передавать аргументы, лучше передавать анонимную функцию, которая самостоятельно будет отвечать за сам вызов.

Кроме того, избегайте использования setInterval в случаях, когда его планировщик может блокировать выполнение JavaScript.

Debounce в JavaScript | Frontend Stuff

Одной из самых больших ошибок, которую я часто вижу при поиске оптимизации существующего кода, является отсутствие функции debounce. Если твое веб-приложение использует JavaScript для выполнения задач, функция debounce необходима для гарантии того, что определённая задача не запускается слишком часто, и тем самым не снижает производительность браузера.

Для тех, кто не знает, что делает функция debounce она ограничивает скорость, с которой функция может срабатывать.

Это предотвращает необходимость обработки каждого пользовательского кода каждым событием, а также значительно уменьшает количество вызовов API, отправляемых на сервер.

Например: у нас есть listener изменения размера window, который выполняет некоторые измерения размеров элемента и (возможно) перемещает несколько элементов. Это не тяжелая задача, но его многократный запуск, после многочисленных изменений, действительно замедлит работу нашего сайта.

Функция debounce может изменить правила игры, если речь идёт о производительности, вызванной событиями scroll, resize и key*.

Вот функция debounce JavaScript (взятая из Underscore.js):







function debounce(func, wait, immediate) {
  let timeout;

  return function executedFunction() {
    const context = this;
    const args = arguments;

    const later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };

    const callNow = immediate && !timeout;

    clearTimeout(timeout);

    timeout = setTimeout(later, wait);

    if (callNow) func.apply(context, args);
  };
};

Debounce — функция высшего порядка, которая возвращает другую функцию (для ясности, здесь она называется executedFunction). Это делается для формирования замыкания (closure) вокруг параметров func, wait и immediate и переменной timeout, чтобы их значения сохранялись.

  • func: функция, которую ты хочешь выполнить после определенного промежутка времени.
  • wait: отрезок времени, который функция debounce будет ожидать после последнего полученного действия, прежде чем выполнять func.
  • immediate: true / false определяет, должна ли функция вызываться вначале. Если true — это означает, что мы вызываем функцию один раз сразу, а затем ждем, пока период ожидания wait не истечет после вызова. По истечении времени следующее событие вызовет функцию и перезапустит debounce. Если false — ждём, пока не истечет период ожидания wait, а затем вызываем функцию.
  • timeout: значение, используемое для обозначения текущего debounce.

Инициируем debounce:

const returnedFunction = debounce(function() {
  
  console.log('debounce');
}, 250);

window.addEventListener('resize', returnedFunction);

Так как функция debounce возвращает функцию, функция executedFunction из первого примера и функция returnedFunction из второго представляют собой одну и ту же функцию. Каждый раз, когда window меняется (resize), то будет выполнятся executedFunction / returnedFunction.

Давай рассмотрим, что происходит внутри функции. Сначала сохраняем контекст this и содержимое arguments, передаваемых в executedFunction. В JavaScript можем вызывать функцию с произвольным числом параметров, даже если их нет в определении функции, arguments всё равно их захватит.

const context = this;
const args = arguments;

Далее, мы объявляем функцию обратного вызова later. Она является функцией, которая выполняется после окончания debounce. Это то, что будет вызвано после истечения setTimeout.

const later = function() {
  timeout = null;
  if (!immediate) func.apply(context, args);
};

timeout = setTimeout(later, wait);

Также, мы объявляем callNow, который определяет, хотим ли мы вызвать функцию в начале. Если да, то вызов выполняется, при условии, что не запущен debounce таймер, то есть timeout !== true.

Событие callNow === true приведет к немедленному выполнению функции func, а затем предотвратит любые последующие вызовы, если не истек debounce таймер.

const callNow = immediate && !timeout;

if (callNow) func.apply(context, args);

Через clearTimeout убираем таймер, который препятствовал выполнению обратного вызова и, таким образом, перезапускаем debounce. Затем (повторно) объявляем timeout, который начинает период ожидания debounce. Если полное время ожидания истекает до другого события, мы выполняем later функцию обратного вызова. Устанавливаем timeout на null — означает что debounce закончилась. Затем он проверяет, хотим ли мы вызвать debounce функцию под конец. Если !immediate, то выполняется func.apply(context, args). Apply выполняет функцию с заданным значением this и массивом arguments.

Вот закомментированная версия функции:

function debounce(func, wait, immediate) {
  let timeout;

  
  return function executedFunction() {
    
    
    const context = this;
    const args = arguments;

    
    const later = function() {
      
      timeout = null;

      
      
      if (!immediate) func.apply(context, args);
    };

    
    const callNow = immediate && !timeout;

    
    
    clearTimeout(timeout);

    
    
    
    timeout = setTimeout(later, wait);

    
    if (callNow) func.apply(context, args);
  };
};

Распространенными сценариями для debounce функции являются события resize, scroll и keyup/keydown. Кроме того, мы должны рассмотреть возможность использовать debounce при любом взаимодействии, которое вызывает чрезмерные вычисления или вызовы API.

Рекомендую тебе взглянуть на Underscore.js и Lodash и их многочисленные вспомогательные функции.

Источники. JavaScript Debounce Function, Debounce in JavaScript

Кооперативный асинхронный JavaScript: таймауты и интервалы — Изучите веб-разработку

В этом руководстве рассматриваются традиционные методы, доступные в JavaScript для асинхронного выполнения кода по истечении заданного периода времени или через регулярный интервал (например, заданное количество раз в секунду), обсуждается, для чего они полезны, и рассматривается их неотъемлемая часть. вопросы.

Предварительные требования: Базовая компьютерная грамотность, хорошее понимание основ JavaScript.
Цель: Чтобы понять асинхронные циклы и интервалы и для чего они нужны.

В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяют им асинхронно выполнять код по истечении определенного временного интервала, а также повторно выполнять блок кода асинхронно, пока вы не скажете, чтобы он остановился. .

Эти функции:

setTimeout ()
Выполнить указанный блок кода один раз по истечении указанного времени.
setInterval ()
Повторно выполнять указанный блок кода с фиксированной временной задержкой между каждым вызовом.
запросAnimationFrame ()
Современная версия setInterval () . Выполняет указанный блок кода перед тем, как браузер в следующий раз перерисовывает изображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.

Асинхронный код, установленный этими функциями, выполняется в основном потоке (по истечении указанного таймера).

Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval () . В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут еще больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после после того, как станет доступным для основного потока. (Другими словами, когда стек пуст.) Вы узнаете больше по этому вопросу по мере прохождения этой статьи.

В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении.В следующих разделах мы покажем вам, как их можно использовать.

Как мы уже говорили, setTimeout () выполняет конкретный блок кода один раз по истечении заданного времени. Принимает следующие параметры:

  • Функция для запуска или ссылка на функцию, определенную в другом месте.
  • Число, представляющее интервал времени в миллисекундах (1000 миллисекунд равняется 1 секунде) ожидания перед выполнением кода. Если вы укажете значение 0 (или опустите значение), функция запустится как можно скорее.(См. Примечание ниже о том, почему он запускается «как можно скорее», а не «немедленно».) Подробнее о том, почему вы, возможно, захотите сделать это позже.
  • Ноль или более значений, представляющих любые параметры, которые вы хотите передать функции при ее запуске.

ПРИМЕЧАНИЕ: Указанное количество времени (или задержка) составляет , а не гарантированное время до выполнения, а минимальное время до выполнения. Обратные вызовы, которые вы передаете этим функциям, не могут выполняться, пока стек в основном потоке не станет пустым.

Как следствие, код типа setTimeout (fn, 0) будет выполняться, как только стек станет пустым, а не сразу . Если вы выполните код вроде setTimeout (fn, 0) , но сразу после запуска цикла, который насчитывает от 1 до 10 миллиардов, ваш обратный вызов будет выполнен через несколько секунд.

В следующем примере браузер будет ждать две секунды перед выполнением анонимной функции, затем отобразит предупреждающее сообщение (посмотрите, как оно работает в реальном времени, и посмотрите исходный код):

  let myGreeting = setTimeout (() => {
  alert ('Здравствуйте, мистерВселенная! ');
}, 2000);  

Указанные вами функции не обязательно должны быть анонимными. Вы можете дать своей функции имя и даже определить ее где-нибудь еще и передать ссылку на функцию в метод setTimeout () . Следующие две версии фрагмента кода эквивалентны первой:

 
let myGreeting = setTimeout (function sayHi () {
  alert ('Здравствуйте, мистер Вселенная!');
}, 2000);


function sayHi () {
  alert («Привет, мистер Вселенная!»);
}

let myGreeting = setTimeout (sayHi, 2000);  

Это может быть полезно, например, если у вас есть функция, которую нужно вызывать как по таймауту, так и в ответ на событие.Но это также может просто помочь сохранить ваш код в чистоте, особенно если обратный вызов тайм-аута занимает больше, чем несколько строк кода.

setTimeout () возвращает значение идентификатора, которое можно использовать для ссылки на тайм-аут позже, например, когда вы хотите его остановить. См. Раздел «Очистка тайм-аутов» (ниже), чтобы узнать, как это сделать.

Передача параметров функции setTimeout ()

Любые параметры, которые вы хотите передать функции, выполняемой внутри setTimeout () , должны быть переданы ей в качестве дополнительных параметров в конце списка.

Например, вы можете реорганизовать предыдущую функцию, чтобы она передавала привет любому переданному ей имени:

  function sayHi (who) {
  alert (`Привет, $ {who}!`);
}  

Теперь вы можете передать имя человека в вызов setTimeout () в качестве третьего параметра:

  let myGreeting = setTimeout (sayHi, 2000, «Мистер Вселенная»);  

Очистка тайм-аутов

Наконец, если тайм-аут был создан, вы можете отменить его до истечения указанного времени, вызвав clearTimeout () , передав ему идентификатор вызова setTimeout () в качестве параметра.Итак, чтобы отменить указанный выше тайм-аут, вы должны сделать это:

  clearTimeout (myGreeting);  

Примечание : См. greeter-app.html для более сложной демонстрации, которая позволяет вам указать имя человека, с которым следует поздороваться, в форме и отменить приветствие с помощью отдельной кнопки (см. Исходный код также).

setTimeout () отлично работает, когда вам нужно запустить код один раз через заданный период времени. Но что происходит, когда вам нужно запускать код снова и снова — например, в случае анимации?

Здесь на помощь приходит setInterval () .Это работает очень похоже на setTimeout () , за исключением того, что функция, которую вы передаете в качестве первого параметра, выполняется многократно с интервалом не меньше, чем количество миллисекунд, заданных вторым параметром отдельно, а не один раз. Вы также можете передать любые параметры, необходимые для выполняемой функции, в качестве последующих параметров вызова setInterval () .

Рассмотрим пример. Следующая функция создает новый объект Date () , извлекает из него строку времени с помощью toLocaleTimeString () и затем отображает ее в пользовательском интерфейсе.Затем он запускает функцию один раз в секунду, используя setInterval () , создавая эффект цифровых часов, которые обновляются один раз в секунду (см. Это в прямом эфире, а также см. Источник):

  function displayTime () {
   пусть дата = новая дата ();
   let time = date.toLocaleTimeString ();
   document.getElementById ('демонстрация'). textContent = время;
}

const createClock = setInterval (displayTime, 1000);  

Так же, как setTimeout () , setInterval () возвращает идентифицирующее значение, которое вы можете использовать позже, когда вам нужно очистить интервал.

Очистка интервалов

setInterval () продолжает выполнение задачи вечно, если вы что-то с ней не сделаете. Возможно, вам понадобится способ остановить такие задачи, иначе вы можете получить ошибки, когда браузер не сможет выполнить какие-либо другие версии задачи или если анимация, обрабатываемая задачей, завершилась. Вы можете сделать это так же, как останавливаете таймауты — передавая идентификатор, возвращенный вызовом setInterval () , в функцию clearInterval () :

  const myInterval = setInterval (myFunction, 2000);

clearInterval (myInterval);  
Активное обучение: Создайте свой собственный секундомер!

С учетом всего сказанного, у нас есть для вас вызов.Возьмите копию нашего примера setInterval-clock.html и измените ее, чтобы создать собственный простой секундомер.

Вам нужно отображать время, как и раньше, но в этом примере вам нужно:

  • Кнопка «Пуск» для запуска секундомера.
  • Кнопка «Стоп», чтобы приостановить / остановить его.
  • Кнопка «Сброс» для сброса времени на 0 .
  • Отображение времени, показывающее количество прошедших секунд, а не фактическое время.

Вот вам несколько советов:

  • Вы можете структурировать и стилизовать разметку кнопок по своему усмотрению; просто убедитесь, что вы используете семантический HTML с хуками, позволяющими захватывать ссылки на кнопки с помощью JavaScript.
  • Вероятно, вы захотите создать переменную, которая начинается с 0 , а затем увеличивается на единицу каждую секунду с использованием постоянного цикла.
  • Этот пример проще создать без использования объекта Date () , как мы это делали в нашей версии, но менее точен — вы не можете гарантировать, что обратный вызов сработает ровно через 1000 мс. Более точным способом было бы запустить startTime = Date.now () , чтобы получить точную метку времени, когда пользователь нажал кнопку запуска, а затем выполнить Date.now () - startTime , чтобы получить количество миллисекунд после нажатия кнопки запуска.
  • Вы также хотите вычислить количество часов, минут и секунд как отдельные значения, а затем отображать их вместе в строке после каждой итерации цикла. На втором счетчике вы можете отработать каждую из них.
  • Как бы вы их рассчитали? Подумайте об этом:
    • Количество секунд в часе: 3600 .
    • Количество минут — это количество секунд, оставшееся после удаления всех часов, разделенное на 60 .
    • Количество секунд — это количество секунд, оставшееся после удаления всех минут.
  • Вы захотите включить начальный ноль в отображаемые значения, если сумма меньше 10 , чтобы они больше походили на традиционные часы / часы.
  • Чтобы приостановить секундомер, вам нужно очистить интервал. Чтобы сбросить его, вам нужно установить счетчик обратно на 0 , очистить интервал, а затем немедленно обновить отображение.
  • Вероятно, вам следует отключить кнопку запуска после ее однократного нажатия и включить ее снова после того, как вы остановили / сбросили ее. В противном случае многократное нажатие кнопки запуска применит к часам несколько секунд setInterval () , что приведет к неправильному поведению.

При работе с setTimeout () и setInterval () следует помнить о нескольких моментах. Давайте рассмотрим их сейчас.

Рекурсивные таймауты

Есть еще один способ использования setTimeout () : вы можете вызвать его рекурсивно для повторного выполнения одного и того же кода вместо использования setInterval () .

В приведенном ниже примере используется рекурсивный setTimeout () для запуска переданной функции каждые 100 миллисекунд:

  пусть я = 1;

setTimeout (function run () {
  console.log (я);
  i ++;
  setTimeout (запустить, 100);
}, 100);  

Сравните приведенный выше пример со следующим — здесь используется setInterval () для достижения того же эффекта:

  пусть я = 1;

setInterval (function run () {
  console.log (я);
  i ++;
}, 100);  
Чем отличаются рекурсивные
setTimeout () и setInterval () ?

Разница между двумя версиями приведенного выше кода невелика.

  • Рекурсивный setTimeout () гарантирует заданную задержку между завершением выполнения кода и следующим вызовом. Отсчет задержки для следующего выполнения начнется только после того, как код завершится, поэтому за исключением времени, затраченного на выполнение кода. В этом примере 100 миллисекунд будут задержкой между завершением выполнения кода и следующим вызовом запуска .
  • Пример с использованием setInterval () работает несколько иначе.Интервал, который вы выбрали , включает времени, затрачиваемое на выполнение кода, который вы хотите запустить. Допустим, для выполнения кода требуется 40 миллисекунд - тогда интервал составит всего 60 миллисекунд.
  • При использовании setTimeout () рекурсивно каждая итерация может вычислять различную задержку перед запуском следующей итерации. Другими словами, значение второго параметра может указывать другое время в миллисекундах для ожидания перед повторным запуском кода.

Когда ваш код может занять больше времени, чем назначенный вами интервал времени, лучше использовать рекурсивный setTimeout () - это сохранит постоянный временной интервал между выполнениями независимо от того, сколько времени потребуется для выполнить, и вы не получите ошибок.

Немедленные таймауты

Использование 0 в качестве значения для setTimeout () планирует выполнение указанной функции обратного вызова как можно скорее, но только после того, как будет запущен основной поток кода.

Например, приведенный ниже код (см. Его в реальном времени) выводит предупреждение, содержащее «Hello», , затем предупреждение, содержащее «World», , как только вы нажимаете OK в первом предупреждении.

  setTimeout (function () {
  alert ('Мир');
}, 0);

alert ('Привет');  

Это может быть полезно в тех случаях, когда вы хотите установить блок кода для запуска, как только весь основной поток завершит работу - поместите его в цикл событий async, чтобы он запускался сразу после этого.

Очистка с помощью clearTimeout () или clearInterval ()

clearTimeout () и clearInterval () оба используют один и тот же список записей для очистки. Интересно, что это означает, что вы можете использовать любой метод для очистки setTimeout () или setInterval () .

Для согласованности следует использовать clearTimeout () для очистки записей setTimeout () и clearInterval () для очистки записей setInterval () .Это поможет избежать путаницы.

requestAnimationFrame () - это специализированная функция постановки в очередь, созданная для эффективного запуска анимации в браузере. Он запускает указанный блок кода до того, как браузер в следующий раз перерисовывает дисплей, что позволяет согласовать выполнение с частотой кадров дисплея устройства.

Он был создан в ответ на предполагаемые проблемы с предыдущими асинхронными функциями, такими как setInterval () , которые, например, не работают с частотой кадров, оптимизированной для устройства, в некоторых случаях отбрасывая кадры.Им также не хватало некоторых оптимизаций, подходящих для анимации, таких как остановка выполнения, если вкладка не активна или анимация прокручивается со страницы, среди прочего.

(Подробнее об этом читайте в CreativeJS.)

Метод принимает в качестве аргумента обратный вызов, который должен быть вызван перед перерисовкой. Это общий шаблон, который вы увидите в его использовании:

  function draw () {
   
   requestAnimationFrame (рисовать);
}

рисовать();  

Идея состоит в том, чтобы определить функцию, в которой обновляется ваша анимация (например,грамм. ваши спрайты перемещаются, счет обновляется, данные обновляются или что-то еще). Затем вы вызываете его, чтобы запустить процесс. В конце функционального блока вы вызываете requestAnimationFrame () со ссылкой на функцию, переданную в качестве параметра, и это дает браузеру команду снова вызвать функцию при следующей перерисовке дисплея. Затем он выполняется непрерывно, так как код рекурсивно вызывает requestAnimationFrame () .

Примечание : Если вы хотите выполнить какую-то простую постоянную DOM-анимацию, CSS-анимация, вероятно, будет быстрее.Они рассчитываются непосредственно внутренним кодом браузера, а не JavaScript.

Если, однако, вы делаете что-то более сложное и вовлекаете объекты, которые не доступны напрямую внутри DOM (например, 2D Canvas API или объекты WebGL), requestAnimationFrame () в большинстве случаев является лучшим вариантом.

Как быстро движется ваша анимация?

Плавность анимации напрямую зависит от частоты кадров анимации и измеряется в кадрах в секунду (fps).Чем выше это число, тем плавнее будет выглядеть ваша анимация до точки.

Поскольку большинство экранов имеют частоту обновления 60 Гц, максимальная частота кадров, к которой вы можете стремиться, составляет 60 кадров в секунду (FPS) при работе с веб-браузерами. Однако большее количество кадров означает больше обработки, что часто может вызывать заикание и пропуски, также известные как пропущенные кадры или jank .

Если у вас есть монитор с частотой обновления 60 Гц и вы хотите достичь 60 FPS, у вас будет около 16.7 миллисекунд ( 1000/60 ) на выполнение кода анимации для рендеринга каждого кадра. Это напоминание о том, что вам нужно помнить об объеме кода, который вы пытаетесь запустить при каждом прохождении цикла анимации.

requestAnimationFrame () всегда пытается максимально приблизиться к этому магическому значению 60 FPS. Иногда это невозможно - если у вас действительно сложная анимация и вы запускаете ее на медленном компьютере, частота кадров будет меньше. Во всех случаях requestAnimationFrame () всегда будет делать все возможное с тем, что у него есть.

Чем requestAnimationFrame () отличается от setInterval () и setTimeout ()?

Давайте поговорим немного подробнее о том, чем метод requestAnimationFrame () отличается от других методов, используемых ранее. Глядя на наш код сверху:

  function draw () {
   
   requestAnimationFrame (рисовать);
}

рисовать();  

Давайте теперь посмотрим, как сделать то же самое с помощью setInterval () :

  function draw () {
   
}

setInterval (ничья, 17);  

Как мы уже говорили ранее, вы не указываете временной интервал для requestAnimationFrame () .Просто он работает максимально быстро и плавно в текущих условиях. Браузер также не тратит время на его запуск, если анимация по какой-то причине выходит за пределы экрана и т. Д.

setInterval () , с другой стороны, требует, чтобы был указан интервал . Мы пришли к нашему окончательному значению 17 по формуле 1000 миллисекунд / 60 Гц , а затем округлили его в большую сторону. Округление - хорошая идея; если вы округлите в меньшую сторону, браузер может попытаться запустить анимацию со скоростью, превышающей 60 кадров в секунду, и в любом случае это не повлияет на плавность анимации.Как мы уже говорили, стандартная частота обновления - 60 Гц.

Включение отметки времени

Фактическому обратному вызову, переданному в функцию requestAnimationFrame () , также может быть задан параметр: значение отметки времени , которое представляет время с момента запуска requestAnimationFrame () .

Это полезно, поскольку позволяет запускать вещи в определенное время и с постоянным темпом, независимо от того, насколько быстрым или медленным может быть ваше устройство. Общий шаблон, который вы бы использовали, выглядит примерно так:

  пусть startTime = null;

function draw (timestamp) {
    if (! startTime) {
      startTime = отметка времени;
    }

   currentTime = отметка времени - startTime;

   

   requestAnimationFrame (рисовать);
}

рисовать();  

Поддержка браузера

requestAnimationFrame () поддерживается в более поздних версиях браузеров, чем setInterval () / setTimeout () .Интересно, что он доступен в Internet Explorer 10 и выше.

Итак, если вам не нужна поддержка более старых версий IE, нет особых причин не использовать requestAnimationFrame () .

Простой пример

Довольно теории! Создадим ваш собственный пример requestAnimationFrame () . Вы собираетесь создать простую «анимацию прядильщика» - такую, которую вы можете увидеть в приложении, когда оно подключено к серверу и т. Д.

Примечание : В реальном примере вам, вероятно, следует использовать анимацию CSS для запуска такой простой анимации.Однако такой пример очень полезен для демонстрации использования requestAnimationFrame () и , и вы с большей вероятностью будете использовать этот вид техники при выполнении чего-то более сложного, например, обновления отображения игры в каждом кадре.

  1. Возьмите базовый шаблон HTML (например, этот).

  2. Поместите пустой элемент

    внутри , затем добавьте внутри него символ ↻. Этот символ круговой стрелки будет действовать как наш счетчик для этого примера.

  3. Примените следующий CSS к шаблону HTML (любым способом). Это устанавливает красный фон на странице, устанавливает высоту на 100% высоты и центрирует

    внутри по горизонтали и вертикали. .

      html {
      цвет фона: белый;
      высота: 100%;
    }
    
    тело {
      рост: наследовать;
      цвет фона: красный;
      маржа: 0;
      дисплей: гибкий;
      justify-content: center;
      align-items: center;
    }
    
    div {
      дисплей: встроенный блок;
      размер шрифта: 10rem;
    }  
  4. Вставьте элемент



    Щелкните по кнопке.


    Через 5 секунд появится предупреждение.





    Explanation:

    В этом примере используется метод setTimeout с основным атрибутом onClick во входном теге внутри body Элемент для вызова функции через пять секунд (5000 миллисекунд).

    Использование таймеров с setTimeout JavaScript

    Использование тайм-аутов и интервалов, таких как setTimeout в JavaScript, для выполнения кода по истечении таймера.

    Выписка

    Поскольку мы работаем с javascript, могут быть случаи, когда нам понадобится таймер для выполнения некоторых функций. Некоторыми примерами могут быть автоматическое закрытие оповещений, игра или автоматическое перенаправление по прошествии определенного времени. В любом случае в нашем распоряжении есть две функции для настройки этих таймеров.Сначала мы рассмотрим setTimeout. SetTimeout укажет единственную функцию, которая будет выполняться по истечении таймера. Для этого мы пишем setTimeout, и первым параметром будет функция, которая будет вызываться по истечении таймера. Итак, в этом случае мы можем просто сказать console.log («истек»), а затем, необязательно, потребуется второй параметр, который представляет собой количество времени в миллисекундах до истечения таймера. Итак, в этом случае мы скажем через одну секунду.Если мы не укажем это время, функция будет вызвана в следующем цикле событий или практически сразу. Теперь возвращаемое значение setTimeout - это идентификатор таймера. Мы хотим захватить этот идентификатор таймера в случае, если мы хотим очистить этот таймер. Поэтому, если мы хотим очистить этот таймер до истечения тайм-аута, мы можем передать идентификатор таймера, который возвращается в функцию clearTimeout, и это очистит наш таймер. Другой вариант - если у нас есть повторяющийся интервал, когда мы хотим вызывать функцию на повторяющейся основе.Для этого мы переходим к setInterval. Точно так же, как setTimeout, мы вызываем setInterval с функцией, и мы просто говорим здесь «повторение истекло», а затем количество времени, которое должно занять каждый интервал. Опять же, это то же самое, что и тайм-аут в предыдущей функции, поэтому мы говорим 2000 миллисекунд, что составит две секунды. Как и при установке тайм-аута, мы также получаем идентификатор таймера. Мы назовем это «intervalId». setInterval имеет сопутствующую функцию clearInterval, которая, как и clearTimeout. Мы можем передать в него наш идентификатор интервала, и мы можем использовать это, чтобы остановить наш интервал от срабатывания нашей функции обратного вызова.Одно предупреждение, когда вы используете их, потому что мы используем функцию обратного вызова, контекст, в котором вызывается функция обратного вызова, может быть не таким, как вы ожидаете. Если мы рассмотрим этот объект здесь, где у него есть свойство сообщения, а затем у него есть эта функция logMessage, и в этом случае он регистрирует свойство сообщения, а затем у нас есть эта функция delayedLog с установленным таймаутом, которая немедленно вызовет функцию сообщения журнала, потому что мы сделали не указывать параметр тайм-аута. Итак, поскольку функция сообщения журнала содержит ключевое слово this ... но эта функция не будет вызываться в контексте этого объекта, this на самом деле будет ссылаться на глобальный контекст, когда он вызывается, а не на наш объект.Итак, вы должны знать, что есть вещи, которые мы можем сделать, чтобы привязать «это» к этому объекту, но мы не собираемся описывать это в этом видео. Наконец, есть еще несколько «подводных камней», о которых следует помнить при использовании этих функций. Во-первых, обратные вызовы можно регулировать, а это означает, что браузер может ограничивать количество вызовов обратного вызова в течение определенного периода времени. Это регулирование еще больше, когда ваша вкладка, на которой выполняется ваш код, является фоновой вкладкой.Также планировщик операционной системы может вызвать функцию позже, чем можно было бы ожидать. Например, если операционная система была занята выполнением какой-либо другой задачи, ваш код может работать не точно вовремя. Последнее, что вы должны знать, это то, что в Firefox обратный вызов может быть отложен во время загрузки страницы, и никакие обратные вызовы не будут выполняться, пока страница не вернется в состояние ожидания. Несмотря на все эти предостережения, эти функции невероятно полезны, и для случаев использования, когда у вас есть таймер, который должен выполнить некоторый код по истечении срока, это будут функции, которые вы захотите вытащить

    . .