Содержание

Справочник 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. Вставьте элемент

setTimeout ()

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

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

  setTimeout (() => {
  
}, 2000)

setTimeout (() => {
  
}, 50)  

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

  const myFunction = (firstParam, secondParam) => {
  
}


setTimeout (myFunction, 2000, firstParam, secondParam)  

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

  const id = setTimeout (() => {
  
}, 2000)


clearTimeout (идентификатор)  

Нулевая задержка

Если вы укажете задержку таймаута на 0 , функция обратного вызова будет выполнена как можно скорее, но после выполнения текущей функции:

  setTimeout (() => {
  приставка.журнал ('после')
}, 0)

console.log ('до')  

напечатает до .

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

Некоторые браузеры (IE и Edge) реализуют метод setImmediate () , который выполняет те же самые функции, но он не является стандартным и недоступен в других браузерах. Но это стандартная функция в Node.js.

setInterval ()

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

  setInterval (() => {
  
}, 2000)  

Вышеупомянутая функция запускается каждые 2 секунды, если вы не прикажете ей остановиться, используя clearInterval , передав ему идентификатор интервала, возвращенный setInterval :

  const id = setInterval (() => {
  
}, 2000)

clearInterval (идентификатор)  

Обычно вызывается clearInterval внутри функции обратного вызова setInterval, чтобы позволить ей автоматически определять, следует ли ей запускать снова или останавливаться.Например, этот код запускает что-то, если App.somethingIWait не имеет значение прибыло :

  const interval = setInterval (() => {
  if (App.somethingIWait === 'прибыл') {
    clearInterval (интервал)
    возвращаться
  }
  
}, 100)  

Рекурсивный setTimeout

setInterval запускает функцию каждые n миллисекунд, не обращая внимания на то, когда функция завершила свое выполнение.

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

Может быть, функция занимает разное время выполнения, в зависимости от условий сети, например:

И, возможно, одно длинное выполнение перекрывает следующее:

Чтобы избежать этого, вы можете запланировать рекурсивный вызов setTimeout по завершении функции обратного вызова:

  const myFunction = () => {
  

  setTimeout (myFunction, 1000)
}

setTimeout (myFunction, 1000)  

для реализации этого сценария:

setTimeout и setInterval доступны в Node.js через модуль Таймеры.

Node.js также предоставляет setImmediate () , что эквивалентно использованию setTimeout (() => {}, 0) , в основном используется для работы с циклом событий Node.js.

Что такое setTimeout (). setTimeout () может привести к неожиданным результатам… | Пиюш Кочхар

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

setTimeout ()

Это функция в JavaScript, которая задерживает выполнение кода.

Части setTimeout ():

Части setTimeout ()

SetTimeout состоит из 3 частей:

  • Обратный вызов
  • Операторы, которые должны выполняться внутри обратного вызова
  • Время в миллисекундах (время задержки)

Приведенный выше код распечатывается «Привет» и «Пока» в терминале через 1 секунду (1000 мс = 1 с).

Кажется, просто! Но если мы не знаем, как setTimeout () ведет себя изнутри, мы можем получить неожиданное поведение.

Пример 1 : Single setTimeout ()
  Выход : Sum = 3 
1 2

Объяснение : В приведенном выше примере setTimeout () имеет задержку в 1 секунду.Таким образом, мы получаем сумму сначала и значения переменных «a» и «b» через 1 секунду.

Пример 2: Блокирование и неблокирование

  Вывод : мы изучаем setTimeout 
10
20
30
Внешние инструкции блокируются
Через 1 секунду
3
Внутренние операторы не блокируют

Объяснение : setTimeout () - это неблокирующий , что означает, что он будет запущен, когда операторы вне него будут выполнены, а затем через одну секунду он будет выполнен.Все другие операторы, которые не являются частью setTimeout (), блокируют , что означает, что ни один другой оператор не будет выполняться до завершения текущего оператора. На рисунке ниже показаны блокирующие и неблокирующие операторы.

Блокирующие и неблокирующие операторы

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

Стек вызовов

Обрабатывает все операторы блокировки и операторы, выходящие из очереди событий.

Цикл событий

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

Очередь событий

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

Стек вызовов, цикл событий и очередь событий

Итак, давайте посмотрим, как выполняется пример 2.

Шаг 1 : Мы разделяем блокирующие и неблокирующие операторы.

Примечание : стек вызовов выполняет по одному оператору за раз. Для простоты все операторы блокировки сразу помещены в стек вызовов.

  Выходные данные : <пустой> 

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

  Выход : мы изучаем setTimeout 
10
20
30
Внешние операторы блокируют

Шаг 3 : Операторы выходят из очереди событий и отправляются в стек вызовов только тогда, когда весь код блокировки в стеке вызовов выполнено, и стек вызовов пуст. Затем выполняются операторы внутри setTimeout (), и, следовательно, мы получаем наш результат.

  Выход : мы изучаем setTimeout 
10
20
30
Внешние инструкции блокируются
Через 1 секунду
3
Внутренние операторы не блокируют

Теперь у нас есть некоторое представление о том, как работает setTimeout ().Давайте рассмотрим еще несколько примеров.

Пример 3: Несколько setTimeout ()

  Выход : a = 10 
b = 20
c = 30
Через 0 секунд
Через 1 секунду
Через 2 секунды
Через 3 секунды

Объяснение : Вышеуказанное пример прост. Помните, что сначала выполняются все блокирующие операторы, пока все setTimeout находятся в ожидании цикла событий. Затем каждый из них попадает в очередь событий один за другим, когда, поскольку 0s имеет самый низкий таймер, он запускается первым, затем выполняется 1s setTimeout (), затем 2s setTimeout () и, наконец, 3s setTimeout ().

Это выглядит примерно так:

Первый, второй, третий и четвертый setTimeout выполняются последовательно, когда они выходят из цикла событий в стек вызовов.

Пример 4: Вложенный setTimeout ()

  Вывод:  ноль 
шесть
один
два
пять
три
четыре

Объяснение : В приведенном выше примере код блокировки выполняется первым, поэтому мы получаем «ноль» и «шесть» в качестве нашего вывода в начале, а внешний setTimeout ожидает 1 с в цикле событий, затем переходит в очередь событий, а затем выходит и выполняется, поэтому теперь печатаются «один» и «два».Но мы видим, что напечатана и «пятерка». Почему? Потому что, как только компилятор JS считывает внутренний setTimeout () (который является неблокирующим оператором) и отправляет его в цикл событий, где он ждет 2 секунды. И пока внутренний setTimeout () ожидает, внешний setTimeout () выполняет все блокирующие операторы внутри себя. Следовательно, печатается «пять». А затем, когда внутренний setTimeout () ожидает, он попадает в очередь событий и выполняется, и именно так мы получаем на выходе «три» и «четыре».

На рисунках ниже показано, как работает приведенный выше код:

Пример 5 : Странное поведение 0 и 1 мс.
  Вывод: 
one
zero

Объяснение : Наш вывод должен быть «ноль» и «единица», потому что 0 мс меньше 1 мс. Но вместо этого «единица» и «ноль». Причина в том, что 1 мс - это такой короткий промежуток времени, который в основном эквивалентен 0 мс, поэтому он немедленно переходит в очередь событий из цикла событий и сначала выполняется через стек вызовов.

Пример 6 : Тайм-аут переполнения
  Вывод:  TimeoutOverflowWarning: 2147483648 не помещается в 32-битное целое число со знаком. 
Длительность тайм-аута была установлена ​​равной 1.
один
ноль

Объяснение : Любое значение времени, превышающее 2³⁰, не помещается в 32-битовое целое число со знаком. Итак, компилятор выдает TimeoutOverflowWarning и автоматически устанавливает значение времени равным 1. И из предыдущего примера, поскольку 1 мс в основном эквивалентно 0 мс.Сначала он выполняется, а затем выполняется setTimeout () 0 мс.

Пример 7: setTimeout и циклы

  Вывод:  3 
3
3

Объяснение : Цикл for является блокирующим оператором, поэтому пока setTimeout () не блокирует. Цикл создает 3 setTimeout, которые переходят в цикл событий, а затем в очередь событий. Пока все setTimeout ожидают в очереди событий, значение «i» уже изменилось на 3 в стеке вызовов. И setTimeouts выводит текущее значение «i».

Одно из решений указанной выше проблемы - использовать let вместо var, которая создает область действия блока.

  Вывод:  0 
1
2

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

  Вывод:  0 
1
2

Таймеры в Node.js | Node.js

Редактировать на GitHub

Модуль таймеров в Node.js содержит функции, которые выполняют код после набора промежуток времени.Таймеры не нужно импортировать через require () , поскольку все методы доступны во всем мире для эмуляции JavaScript API браузера. Чтобы полностью понять, когда будут выполняться функции таймера, рекомендуется читайте на Node.js Цикл событий.

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

setTimeout () может использоваться для планирования выполнения кода после назначенного количество миллисекунд. Эта функция похожа на window.setTimeout () из JavaScript API браузера, однако строка кода не может быть передана быть исполненным.

setTimeout () принимает функцию для выполнения в качестве своего первого аргумента, а миллисекундная задержка, определяемая как число в качестве второго аргумента.Дополнительный Также могут быть включены аргументы, которые будут переданы функции. Здесь пример этого:

  function myFunc (arg) {
  console.log (`arg was => $ {arg}`);
}

setTimeout (myFunc, 1500, «напуганный»);
  

Вышеупомянутая функция myFunc () будет выполняться как можно ближе к 1500 миллисекунды (или 1,5 секунды), насколько это возможно из-за вызова setTimeout () .

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

setTimeout () возвращает объект Timeout , который можно использовать для ссылки на установленный тайм-аут. Этот возвращенный объект можно использовать для отмены тайм-аута ( см. clearTimeout () ниже), а также изменить поведение выполнения (см. unref () ниже).

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

Первым аргументом для setImmediate () будет функция, которую нужно выполнить.Любой последующие аргументы будут переданы функции при ее выполнении. Вот пример:

  console.log («до немедленного»);

setImmediate ((arg) => {
  console.log (`немедленное выполнение: $ {arg}`);
}, 'так немедленно');

console.log ('сразу после');
  

Вышеупомянутая функция, переданная в setImmediate () , будет выполняться после всех запускаемых код выполнен, и вывод консоли будет:

  до немедленного
сразу после
выполнение немедленно: так немедленно
  

setImmediate () возвращает объект Immediate , который можно использовать для отмены запланированное немедленное (см. clearImmediate () ниже).

Не путайте setImmediate () с process.nextTick () . Есть некоторые основные различия между ними. Во-первых, будет запущен process.nextTick () . перед любые немедленные , которые установлены, а также перед любым запланированным вводом-выводом. Во-вторых, process.nextTick () не очищается, то есть один раз код был запланирован для выполнения с process.nextTick () , выполнение не может быть остановлен, как и при нормальной работе.Обратитесь к этому руководству чтобы лучше понять работу process.nextTick () .

Если есть блок кода, который должен выполняться несколько раз, setInterval () можно использовать для выполнения этого кода. setInterval () принимает функцию аргумент, который будет выполняться бесконечное количество раз с заданной миллисекундой задержка в качестве второго аргумента. Как и setTimeout () , дополнительные аргументы могут быть добавлены после задержки, и они будут переданы в вызов функции.Также как и setTimeout () , задержка не может быть гарантирована из-за операций которые могут удерживать цикл событий и поэтому должны рассматриваться как примерная задержка. См. Пример ниже:

  function intervalFunc () {
  console.log («Не могу меня остановить!»);
}

setInterval (intervalFunc, 1500);
  

В приведенном выше примере intervalFunc () будет выполняться примерно каждые 1500 миллисекунды или 1,5 секунды, пока он не будет остановлен (см. ниже).

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

Что можно сделать, если нужно отменить объект Timeout или Immediate ? setTimeout () , setImmediate () и setInterval () возвращают объект таймера который может использоваться для ссылки на набор Timeout или Immediate object. Передавая указанный объект в соответствующую функцию clear , выполнение этот объект будет полностью остановлен. Соответствующие функции: clearTimeout () , clearImmediate () и clearInterval () .См. Пример ниже для примера каждого:

  const timeoutObj = setTimeout (() => {
  console.log ('тайм-аут вне времени');
}, 1500);

constmediateObj = setImmediate (() => {
  console.log («немедленное выполнение»);
});

const intervalObj = setInterval (() => {
  console.log ('опрос интервала');
}, 500);

clearTimeout (timeoutObj);
clearImmediate (mediateObj);
clearInterval (intervalObj);
  

Помните, что Timeout объектов возвращаются setTimeout и setInterval .Объект Timeout предоставляет две функции, предназначенные для увеличения времени ожидания Timeout . поведение с unref () и ref () . Если запланирован объект Timeout используя функцию set , для этого объекта можно вызвать unref () . Это изменится поведение немного, и не вызывать Тайм-аут объект , если он последний код для выполнения . Объект Timeout не будет поддерживать процесс, ожидая выполнить.

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

  const timerObj = setTimeout (() => {
  console.log ('я буду работать?');
});




timerObj.unref ();



setImmediate (() => {
  timerObj.ref ();
});
  

Цикл событий и таймеры - это гораздо больше, чем это руководство покрыл. Чтобы узнать больше о внутреннем устройстве Node.js Цикл событий и то, как таймеры работают во время выполнения, проверьте это руководство по Node.js: Цикл событий Node.js, таймеры и process.nextTick ().

Почему обещания быстрее, чем setTimeout ()?

1. Эксперимент

Попробуем поэкспериментировать. Что выполняется быстрее: немедленно выполненное обещание или немедленный тайм-аут (также известный как тайм-аут 0 миллисекунд)?

  Обещание.resolve (1) .then (функция resolve () {
  console.log («Решено!»);
});

setTimeout (функция timeout () {
  console.log («Истекло время ожидания!»);
}, 0);


  

Promise.resolve (1) - статическая функция, которая возвращает немедленно обработанное обещание. setTimeout (callback, 0) выполняет обратный вызов с задержкой 0 миллисекунд.

Откройте демо и проверьте консоль. Вы заметите, что «Решено!» Сначала регистрируется , затем «Тайм-аут завершен!» .Моментально выполненное обещание обрабатывается быстрее, чем немедленный тайм-аут.

Может ли процесс обещания выполняться быстрее, потому что Promise.resolve (true) .then (...) был вызван до setTimeout (..., 0) ? Достаточно честный вопрос.

Давайте немного изменим условия эксперимента и сначала вызовем setTimeout (..., 0) :

  setTimeout (function timeout () {
  console.log («Истекло время ожидания!»);
}, 0);

Promise.resolve (1) .then (function resolve () {
  приставка.log («Решено!»);
});


  

Откройте демо и посмотрите на консоль. Хм… результат тот же!

setTimeout (..., 0) вызывается перед Promise.resolve (true) .then (...) . Однако "Решено!" все еще регистрируется до «Истекло время ожидания!» .

Эксперимент продемонстрировал, что немедленно обработанное обещание обрабатывается до немедленного тайм-аута. Большой вопрос… почему?

2. Цикл событий

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

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

Стек вызовов - это структура LIFO (последний вошел, первый ушел), в которой хранится контекст выполнения, созданный во время выполнения кода. Проще говоря, стек вызовов выполняет функции.

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

Очередь задач (также называемая макростазами) представляет собой структуру FIFO (First In, First Out), которая содержит обратные вызовы асинхронных операций, готовых к выполнению. Например, обратный вызов истекшего времени setTimeout () - готовый к выполнению - помещается в очередь задач.

Очередь заданий (также называемая микрозадачами) представляет собой структуру FIFO (First In, First Out), которая содержит обратные вызовы обещаний, которые готовы к выполнению. Например, обратные вызовы разрешения или отклонения выполненного обещания помещаются в очередь заданий.

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

3. Очередь заданий и очередь задач

Давайте еще раз посмотрим на эксперимент с точки зрения цикла событий. Сделаю пошаговый анализ выполнения кода.

A) Стек вызовов выполняет setTimeout (..., 0) и расписывает таймер. timeout () обратный вызов хранится в веб-API :

  setTimeout (функция timeout () {console.log ('Истекло время ожидания!');}, 0);
Promise.resolve (1) .then (function resolve () {
  console.log («Решено!»);
});  

B) Стек вызовов выполняет Promise.resolve (true). Затем (resolve) и назначают разрешение обещания. resolved () обратный вызов хранится в веб-API :

  setTimeout (function timeout () {
  приставка.log ('Истекло время ожидания!');
}, 0);

Promise.resolve (1) .then (функция resolve () {console.log ('Решено!');});  

C) Обещание выполняется немедленно, также как и таймаут таймаута немедленно. Таким образом, обратный вызов таймера timeout () - поставлен в очередь в очередь задач, обратный вызов обещания resolve () - поставлен в очередь в очередь заданий:

D) Теперь самое интересное: приоритеты цикла событий удаляют задания из очереди над задачами.Цикл событий извлекает из очереди заданий обратный вызов обещания resolve () и помещает его в стек вызовов. Затем стек вызовов выполняет обратный вызов обещания resolve () :

  setTimeout (function timeout () {
  console.log («Истекло время ожидания!»);
}, 0);

Promise.resolve (1) .then (function resolve () {
  console.log ('Решено!');});  

"Решено!" зарегистрирован в консоли.

E) Наконец, цикл обработки событий удаляет обратный вызов таймера timeout () из очереди задач в стек вызовов.Затем стек вызовов выполняет обратный вызов таймера timeout () :

  setTimeout (function timeout () {
  console.log ('Истекло время ожидания!');}, 0);

Promise.resolve (1) .then (function resolve () {
  console.log («Решено!»);
});  

'Истекло время ожидания!' зарегистрирован в консоли.

Стек вызовов пуст. Выполнение скрипта завершено.

4. Резюме

Почему немедленно обработанное обещание обрабатывается быстрее, чем немедленный таймер?

Из-за цикла событий приоритеты исключение заданий из очереди заданий (в которой хранятся обратные вызовы выполненных обещаний) по задачам из очереди задач (в которой хранятся просроченные обратных вызовов setTimeout () ).

JavaScript >> Window >> setTimeout

Синтаксис:
[intervalID =] window.setTimeout (выражение / функция, миллисекунды)

Этот метод используется для вызова функции или оценки выражения через указанное количество миллисекунд.

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

Возвращаемое значение для этого метода - идентификатор установленного таймера.

Примеры

Код:
function winClose () {
myWindow.close ()
}

myWindow = window.open ("", "tinyWindow", 'ширина = 150, высота = 110')
myWindow.document.write ("Это окно закроется автоматически через пять секунд. Спасибо за терпение »)
self.setTimeout ('winClose ()', 5000)

Explanation:

В этом примере открывается новое окно и используется setTimeout метод для вызова функции winClose (), которая закрывает ее через пять секунд (5000 миллисекунд).

Код:






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


Через 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 обратный вызов может быть отложен во время загрузки страницы, и никакие обратные вызовы не будут выполняться, пока страница не вернется в состояние ожидания. Несмотря на все эти предостережения, эти функции невероятно полезны, и для случаев использования, когда у вас есть таймер, который должен выполнить некоторый код по истечении срока, это будут функции, которые вы захотите вытащить

. .