Понимание таймеров в JavaScript. Callback-функции, setTimeout, setInterval и requestAnimationFrame
В предыдущей статье Путешествие по JavaScript таймерам в сети от Нолана Лоусона многие в сети и в офлайне высказали недопонимание того, о чём там пишется, но тема всем показалась очень интересной и занимательной. Поэтому я решил исправиться и продолжить тему более детально, собрав хороший материал по каждому таймеру, где объясняется конкретно то, как он работает.
В статье присутствует адаптированный и дополненный материал с переводами статей:
Мой Твиттер — там много из мира фронтенда, да и вообще поговорим. Подписывайтесь, будет интересно: ) ✈️
Что такое callback-функции?
Или просто колбэки. Прежде, чем вообще начинать понимать таймеры и асинхронность, нужно разобраться с callback функциями. Что же это такое?
Простое определение: колбэк это функция, которая выполнится после другой функции, завершившей своё выполнение. Следовательно, отсюда и название, ‘call back’.
Определение посложнее: В JavaScript, функции это объекты. Поэтому, функции, могут брать другие функции в виде аргументов и также могут быть возвращены другими функциями. Функции которые так делают, называются функциями высшего порядка. Любая функция, которая передается как аргумент — именуется callback-функцией.
Что-то много слов. Давайте посмотрим на примерах и разберемся поглубже.
Зачем нам вообще колбэки?
По одной простой и важной причине — JavaScript это событийно-ориентированный язык. Это говорит нам о том, что вместо ожидания ответа для последующего шага, JavaScript продолжит выполнение, следя за другими событиями (ну или ивентам, кому как удобнее). Давайте взглянем на простой пример:
Как вы и ожидали, функция first выполнится первой, а функция second выполнится второй — все это выдаст в консоль следующее:
Но что, если функция first будет содержать код, который не может быть немедленно выполнен. Для примера, API запрос, где нам нужно отправить информацию, а затем подождать ответ? Чтобы симулировать такое действие, мы применим setTimeout (дальше будет подробнее про него), который является функцией JavaScript, вызывающей другую функцию после определенного количества времени. То есть, мы задержим нашу функцию на 500 миллисекунд, чтобы симулировать API запрос. Таким образом, наш новый код будет выглядеть так:
Пока что совершенно неважно, понимаете ли вы то, как работает setTimeout() . Всё, что важно — это то, чтобы вы увидели, что мы отсрочили console.log(1) на 500 миллисекунд. И так, что случится, когда мы вызовем наши функции?
Пусть даже мы и вызываем first() первой, мы выводим в лог результат этой функции, после функции second() .
Не то чтобы JavaScript не выполняет наши функции в том порядке, в котором нам надо, просто вместо этого, JavaScript не ждал ответа от first() перед тем, чтобы идти дальше, для выполнения second() .
Так зачем вам это? А затем, что вы не можете просто вызывать одну функцию за другой и надеяться, что они выполнятся в правильном порядке. Колбэки это способ убедиться в том, что конкретный код не выполняется перед другим отрезком кода, который ещё не закончил своё выполнение.
Создаём callback
Достаточно разговоров, давайте создадим callback!
Во-первых, откройте Chrome Developer Console (Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J) и введите следующий код функции:
Итак, выше мы создали функцию doHomework . Наша функция берёт одну переменную, это subject . Вызовите функцию, введя следующее в вашу консоль:
Теперь давайте добавим callback — как последний параметр в функции doHomework() , мы можем передать callback. Callback-функция теперь является вторым аргументом вызова doHomework() .
Как вы видите, введя код выше в вашу консоль, вы получите один за другим два оповещения. Первое starting homework и второе, которое последует за ним finished homework.
Но колбэкам необязательно всегда быть определенными в вызове функции. Они могут быть определены где угодно в коде. Например как тут:
Результат этого примера точно такой же, как и результат предыдущего, но настройка идёт немного по-другому. Как вы видите, мы передали функцию alertFinished как аргумент, во время вызова функции doHomework() .
Пример из реального мира
На прошлой неделе я опубликовал статью о том, как создать бота в Twitter в 38 строчек кода. Единственной причиной, по которой код в этой статье работал, был API от Twitter. Когда вы делаете запросы по API, вам нужно подождать ответа, перед тем как вы сможете как-то с ним работать и соответственно на него воздействовать. Вот то, как выглядит сам запрос.
T.get просто означает то, что мы делаем get запрос к Twitter.
У этого запроса есть три параметра: ‘search/tweets’ , которые служит маршрутом для нашего запроса, params которые являются параметрами поиска и дальше идёт анонимная функция, которая и является колбэком.
Колбэк тут крайне важен, так как нам нужно подождать ответа сервера, перед тем как идти дальше в выполнении кода. Мы понятия не имеем, будет ли наш API запрос успешным или нет, так что после отправки наших параметров к search/tweets через запрос get — мы ждём. Как только Twitter ответит, вызовется наша callback-функция. Twitter либо отправит err объект (т.е. ошибку) или объект response . В нашем колбэке мы можем применить if() , чтобы определить был ли наш запрос проведен успешно или нет, а за тем уже соответственно работать с новыми данными.
В этой теме можно было бы ещё коснуться рекурсий, но это немного другая песня, которая требует более детального понимания.
Таймеры setTimeout и setInterval
Каждый блок JavaScript кода, как правило, выполняется синхронно. Но в коробке у JavaScript уже есть нативные функции (таймеры), которые позволяют задерживать выполнение какого-либо кода.
Это setTimeout() и setInterval() . Они позволят вам запустить кусок JavaScript кода в определенный момент в будущем. Такой подход называется “отложенным вызовом”. Далее вы узнаете как работают эти два метода и увидите несколько практических примеров.
setTimeout()
Эту функцию вы видели выше, а сейчас узнаете про неё ещё детальнее. Она используется в основном в тех случаях, если вы хотите запустить вашу функцию через конкретное количество миллисекунд после вызова самого setTimeout() . Синтаксис для этого метода такой:
Тут expression в JavaScript коде запустится по прошествии миллисекунд, указанных в аргументе timeout .
setTimeout() также возвращает ID для тайм-аута, чтобы его можно было отследить. Но в основном оно используется для метода clearTimeout() , который останавливает выполнение отложенной функции. В качестве аргумента тут нужно вставить ID (название) функции.
Вот ещё один пример:
При нажатии на кнопку запускается setTimeout() метод. Выражение, запуск которого по вашему предусмотрению должен произойти с задержкой в 4000ms или 4 секунды, уже передано.
Тут стоит обратить внимание на то, что setTimeout() не останавливает выполнение дальнейшего скрипта во время периода тайм-аута. Он просто откладывает выполнение указанного блока кода на заложенное количество времени. После вызова функции setTimeout() , скрипт продолжит выполняться обычным образом, с таймером на фоне.
То, что выше — это простой пример со всем кодом для alert бокса в setTimeout вызове. На практике же, вы будете вызывать функции внутри таймеров гораздо чаще. Следующий пример даст вам лучшее понимание о вызове функций с помощью setTimeout() .
Для примера, код ниже, вызывает sayHello() через одну секунду:
Вы можете также передавать аргументы вместе с функцией, например, как тут:
Как вы видите, для setTimeout() сначала передаётся функция аргумент, затем время задержки и уже только потом аргументы для функции аргумента(пардон за каламбур).
Если первый аргумент это строка, то JavaScript может создать из неё функцию. Так что вот это тоже сработает:
Но применение такого метода не рекомендуется, лучше используйте функции, как тут:
setInterval()
Эта функция, как и предполагается из названия, в основном используется для задержки функций, которые будут выполняться снова и снова, например анимации. Функция setInterval() очень близка к setTimeout() , у них даже такой же синтаксис:
Но разница тут вот в чём. setTimeout() запускает expression только единожды, в то время, как setInterval() продолжает запускать expression на регулярной основе после заданного временного интервала, пока вы не скажете стоп.
Для того, чтобы остановить последующие вызовы в setInterval() , вам нужно вызывать clearInterval(timerId) , где timerId это имя функции setInterval .
Когда вам нужно использовать setInterval() ? Когда вам не нужно вызывать setTimeout() в конце спланированной функции. Также, во время использования setInterval() , фактически не существует задержки между одним срабатыванием настоящего выражения и последующим. А в setTimeout() существует относительно долгая задержка, во время выполнения выражения, вызова функции и выставления нового setTimeout . Так что если вам нужен обычный точный таймер и надо, чтобы что-то делалось повторно после определенного временного интервала, тогда setInterval это ваш выбор.
Итак, сейчас мы подобрались к самому интересному. А именно к requestAnimationFrame . А про него нужно рассказать максимально подробно.
requestAnimationFrame()
Если вы используете анимации в своих веб-приложениях, то вы в любом случае хотите, чтобы они выполнялись как по маслу. И самым простым способом для этого является использование requestAnimationFrame , ну или просто rAF — метода который делает это непринужденно и легко.
Использование этого метода позволяет браузеру справиться с некоторыми затруднительными задачами связанными с анимацией, например такими как управление частотой кадров.
До этого разработчики использовали setTimeout и setInterval , чтобы создавать анимации. Проблема тут была в том, что для того, чтобы анимации были плавными, браузер зачастую отрисовывал кадры быстрее, чем они могут показаться на экране. Что вело к ненужным вычислениям. Также ещё одной проблемой в использовании setInterval или setTimeout было то, что эти анимации продолжали работать, даже если страница не находилась в поле видимости пользователя.
Почему нужно использовать requestAnimationFrame?
Чем же он так хорош requestAnimationFrame ? Давайте посмотрим на некоторые вещи, которые requestAnimationFrame делает значительно лучше, чем setInterval и setTimeout .
Оптимизация браузером
Использование requestAnimationFrame даёт браузеру возможность оптимизировать анимации, чтобы делать их плавнее и более ресурсоэффективными. Не будем сильно вдаваться в детали того, как браузер это делает, просто знайте, что requestAnimationFrame исключает возможность ненужных отрисовок и может связывать вместе несколько анимаций в одно целое и цикл перерисовки.
Анимации работают, когда их видно
Используя requestAnimationFrame ваши анимации будут работать только тогда, когда вкладка со страницей видима пользователю. А это означает меньшее CPU, GPU и использование памяти, что приводит нас к последнему моменту эффективности.
Меньшее потребление питания
Оптимизации, упомянутые в предыдущих двух моментах помогают сократить количество “мусорных процессов”, которые нужно совершить устройству, чтобы создать анимацию и, следовательно, это ведет к бережному энергопотреблению. Это особенно важно для мобильных устройств, которые обычно имеют относительно короткие сроки работы батареи.
Используем requestAnimationFrame
Этому методу должна быть передана колбэк функция, которая отвечает за отрисовку одного кадра вашей анимации. Для того, чтобы создать полную анимацию, вам понадобится сделать этот колбэк рекурсивным.
Временная метка с высоким разрешением DOMHighResTimeStamp передаётся колбэку. Вам не понадобится всегда это использовать, но это может быть довольно полезным для некоторых анимаций.
Пример ниже показывает то, как настроить рекурсивную функцию, которая использует requestAnimationFrame .
Стоит упомянуть, что у вас есть только 16.67 миллисекунд, чтобы отрендерить каждый кадр. С точки зрения времени это не очень хорошо, так что вам нужно быть осторожным с тем, что вы хотите выполнить внутри колбэк функции. Если ваш кадр требует больше 16.67 секунд на обработку, то анимация может выйти не совсем плавной.
requestAnimationFrame отдаст requestID , который может быть использован для отмены запланированного кадра анимации.
Отменяем кадры анимации
Чтобы отменить кадр анимации вы можете использовать метод cancelAnimationFrame . Этот метод должен принять requestID для кадра, который вы хотите отменить.
Позже вы узнаете то, как отслеживать актуальный requestID .
Полифил
Есть великолепный полифилл для requestAnimationFrame , который разработал Эрик Мюллер из Opera и который далее доработали Пол Айриш и Тино Зийдел. Код лежит тут.
Создаём простую демку с requestAnimationFrame
После того, как вы поняли теорию по requestAnimationFrame , давайте создадим простую демку. Вот она.
Подготавливаем HTML и CSS
Откройте свой любимый текстовый редактор и создайте файл под названием index.html . После этого, добавьте следующий код в новый файл.
В этой разметке вы указываете число кнопок, которые будут использоваться для запуска, остановки и сброса анимации. Вы также определите canvas, в котором и будет происходить сама анимация.
Стоит обратить внимание, что тут есть два файла в <script>, который находится в конце этой разметки. raf-polyfill.js это полифил о котором мы говорили в предыдущей секции. Убедитесь в том, что вы скачали этот файл и сохранили его в той же директории, что и index.html . Вам также надо будет скопировать style.css отсюда в папку вашего проекта.
Настраиваем JavaScript
Теперь, как только вы разобрались с HTML и CSS, настало время писать JavaScript код, который будет обрабатывать отрисовку анимации в <canvas> . Если до этого вы не использовали Canvas API, то не беспокойтесь, я объясню всё что нужно по мере прочтения статьи.
Создайте новый файл в папке проекта под название script.js и добавьте туда этот код:
Вы создали три переменные и ассоциировали их с кнопками из разметки.
Дальше вам надо написать немного кода, чтобы настроить Canvas. Скопируйте этот код в свой script.js файл.
Тут сначала мы создаем переменную под названием сanvas и ассоциируем её с элементом из разметки. Далее вы задаёте 2d контекст отрисовки для него. Это даёт нам методы для отрисовки объектов на нём, так же как и методы контроля стилей этих объектов.
Следующая строка кода выставляет свойству fillStyle для контекста отрисовки #212121 .
requestID переменная будет использоваться, чтобы отслеживать requestID , возвращенный методом requestAnimationFrame .
Переменные posX , boxWidth и pixelPerFrame используются для выставления позиции блоку при отрисовке; ширина блока; и число пикселей на которое блок должен двигаться при каждом кадре.
Под конец вы вызываете контекст отрисовки методом fillReact , передав ему X и Y координаты положения прямоугольника вместе с его высотой и шириной.
Написание анимированной функции
Далее вам надо написать функцию animate , которая будет ответственна за прорисовку кадров.
Скопируйте следующий код в ваш script.js файл.
Вызов requestAnimateFrame вверху функции запланирует следующий кадр анимации. Он размещается сначала, так как мы можем подобраться как можно ближе к 60 FPS, при использовании setTimeout фолбэка, которое применяет полифил.
Далее у вас будет if проверка, которая проверяет достиг ли блок крайней правой стороны canvas’а. Если блок ещё этого не сделал, то вы используете clearRect метод, чтобы удалить блок, отрисованный в предыдущем кадре и затем отрисовать блок на новой позиции с использованием fillRect . Если же блок достиг конца canvas’а, то вы вызываете cancelAnimationFrame , чтобы отменить планирование кадра в начале animate функции. И под конец, вы обновляете posX переменную с позицией на которой блок должен быть отрисован при следующем кадре.
Цепляем кнопки
Последнее, что нам нужно сделать для того, чтобы демо работало — это настроить срабатывания по событиям для кнопок старта, остановки и сброса. Добавьте следующий код в ваш script.js файл.
Тут у нас три обработчика событий. Первые два запускают и останавливают анимацию, а последний запускается при нажатии на кнопку reset. Это выставит переменной posX значение 0 . Также это очистит canvas — мы не так сильно обеспокоены производительностью, так что это нормально делать таким ленивым способом — и отрисовывать блок обратно на его стартовой позиции.
setTimeout()
The global setTimeout() method sets a timer which executes a function or specified piece of code once the timer expires.
Syntax
Parameters
A function to be executed after the timer expires.
An alternative syntax that allows you to include a string instead of a function, which is compiled and executed when the timer expires. This syntax is not recommended for the same reasons that make using eval() a security risk.
The time, in milliseconds that the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute «immediately», or more accurately, the next event cycle.
Note that in either case, the actual delay may be longer than intended; see Reasons for delays longer than specified below.
Also note that if the value isn’t a number, implicit type coercion is silently done on the value to convert it to a number — which can lead to unexpected and surprising results; see Non-number delay values are silently coerced into numbers for an example.
param1 , …, paramN Optional
Additional arguments which are passed through to the function specified by functionRef .
Return value
The returned timeoutID is a positive integer value which identifies the timer created by the call to setTimeout() . This value can be passed to clearTimeout() to cancel the timeout.
It is guaranteed that a timeoutID value will never be reused by a subsequent call to setTimeout() or setInterval() on the same object (a window or a worker). However, different objects use separate pools of IDs.
Description
Timeouts are cancelled using clearTimeout() .
To call a function repeatedly (e.g., every N milliseconds), consider using setInterval() .
Non-number delay values are silently coerced into numbers
If setTimeout() is called with delay value that’s not a number, implicit type coercion is silently done on the value to convert it to a number. For example, the following code incorrectly uses the string «1000» for the delay value, rather than the number 1000 – but it nevertheless works, because when the code runs, the string is coerced into the number 1000 , and so the code executes 1 second later.
But in many cases, the implicit type coercion can lead to unexpected and surprising results. For example, when the following code runs, the string «1 second» ultimately gets coerced into the number 0 — and so, the code executes immediately, with zero delay.
Therefore, don’t use strings for the delay value but instead always use numbers:
Working with asynchronous functions
setTimeout() is an asynchronous function, meaning that the timer function will not pause execution of other functions in the functions stack. In other words, you cannot use setTimeout() to create a «pause» before the next function in the function stack fires.
See the following example:
Notice that the first function does not create a 5-second «pause» before calling the second function. Instead, the first function is called, but waits 5 seconds to execute. While the first function is waiting to execute, the second function is called, and a 3-second wait is applied to the second function before it executes. Since neither the first nor the second function’s timers have completed, the third function is called and completes its execution first. Then the second follows. Then finally the first function is executed after its timer finally completes.
To create a progression in which one function only fires after the completion of another function, see the documentation on Promises.
The «this» problem
When you pass a method to setTimeout() , it will be invoked with a this value that may differ from your expectation. The general issue is explained in detail in the JavaScript reference.
Code executed by setTimeout() is called from an execution context separate from the function from which setTimeout was called. The usual rules for setting the this keyword for the called function apply, and if you have not set this in the call or with bind , it will default to the window (or global ) object. It will not be the same as the this value for the function that called setTimeout .
See the following example:
The above works because when myMethod is called, its this is set to myArray by the call, so within the function, this[sProperty] is equivalent to myArray[sProperty] . However, in the following:
The myArray.myMethod function is passed to setTimeout , then when it’s called, its this is not set, so it defaults to the window object.
There’s also no option to pass a thisArg to setTimeout as there is in Array methods such as forEach() and reduce() . As shown below, using call to set this doesn’t work either.
Solutions
Use a wrapper function
A common way to solve the problem is to use a wrapper function that sets this to the required value:
The wrapper function can be an arrow function:
Use bind()
Alternatively, you can use bind() to set the value of this for all calls to a given function:
Passing string literals
Passing a string instead of a function to setTimeout() has the same problems as using eval() .
A string passed to setTimeout() is evaluated in the global context, so local symbols in the context where setTimeout() was called will not be available when the string is evaluated as code.
Reasons for delays longer than specified
There are a number of reasons why a timeout may take longer to fire than anticipated. This section describes the most common reasons.
Nested timeouts
As specified in the HTML standard, browsers will enforce a minimum timeout of 4 milliseconds once a nested call to setTimeout has been scheduled 5 times.
This can be seen in the following example, in which we nest a call to setTimeout with a delay of 0 milliseconds, and log the delay each time the handler is called. The first four times, the delay is approximately 0 milliseconds, and after that it is approximately 4 milliseconds:
Timeouts in inactive tabs
To reduce the load (and associated battery usage) from background tabs, browsers will enforce a minimum timeout delay in inactive tabs. It may also be waived if a page is playing sound using a Web Audio API AudioContext .
The specifics of this are browser-dependent:
- Firefox Desktop and Chrome both have a minimum timeout of 1 second for inactive tabs.
- Firefox for Android has a minimum timeout of 15 minutes for inactive tabs and may unload them entirely.
- Firefox does not throttle inactive tabs if the tab contains an AudioContext .
Throttling of tracking scripts
Firefox enforces additional throttling for scripts that it recognizes as tracking scripts. When running in the foreground, the throttling minimum delay is still 4ms. In background tabs, however, the throttling minimum delay is 10,000 ms, or 10 seconds, which comes into effect 30 seconds after a document has first loaded.
Late timeouts
The timeout can also fire later than expected if the page (or the OS/browser) is busy with other tasks. One important case to note is that the function or code snippet cannot be executed until the thread that called setTimeout() has terminated. For example:
Will write to the console:
This is because even though setTimeout was called with a delay of zero, it’s placed on a queue and scheduled to run at the next opportunity; not immediately. Currently-executing code must complete before functions on the queue are executed, thus the resulting execution order may not be as expected.
Deferral of timeouts during pageload
Firefox will defer firing setTimeout() timers while the current tab is loading. Firing is deferred until the main thread is deemed idle (similar to window.requestIdleCallback()), or until the load event is fired.
WebExtension background pages and timers
In WebExtensions, setTimeout() does not work reliably. Extension authors should use the alarms API instead.
Maximum delay value
Browsers store the delay as a 32-bit signed integer internally. This causes an integer overflow when using delays larger than 2,147,483,647 ms (about 24.8 days), resulting in the timeout being executed immediately.
Examples
Setting and clearing timeouts
The following example sets up two simple buttons in a web page and hooks them to the setTimeout() and clearTimeout() routines. Pressing the first button will set a timeout which shows a message after two seconds and stores the timeout id for use by clearTimeout() . You may optionally cancel this timeout by pressing on the second button.
Name already in use
javascript-tutorial-ru / 1-js / 7-js-misc / 3-settimeout-setinterval / article.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
setTimeout и setInterval
Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.
В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.
func/code : Функция или строка кода для исполнения. Строка поддерживается для совместимости, использовать её не рекомендуется.
delay : Задержка в миллисекундах, 1000 миллисекунд равны 1 секунде.
arg1 , arg2 . : Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.
Исполнение функции произойдёт спустя время, указанное в параметре delay .
Например, следующий код вызовет func() через одну секунду:
С передачей аргументов (не сработает в IE9-):
Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.
То есть такая запись тоже сработает:
Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.
Вместо них используйте анонимные функции, вот так:
Отмена исполнения clearTimeout
Функция setTimeout возвращает числовой идентификатор таймера timerId , который можно использовать для отмены действия.
В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.
Как видно из alert , в браузере идентификатор таймера является обычным числом. Другие JavaScript-окружения, например Node.JS, могут возвращать объект таймера, с дополнительными методами.
Такие разночтения вполне соответствуют стандарту просто потому, что в спецификации JavaScript про таймеры нет ни слова.
Таймеры — это надстройка над JavaScript, которая описана в секции Timers стандарта HTML5 для браузеров и в документации к Node.JS — для сервера.
Метод setInterval имеет синтаксис, аналогичный setTimeout .
Смысл аргументов — тот же самый. Но, в отличие от setTimeout , он запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом clearInterval(timerId) .
Следующий пример при запуске станет выводить сообщение каждые две секунды, пока не пройдёт 5 секунд:
Важная альтернатива setInterval — рекурсивный setTimeout :
В коде выше следующее выполнение планируется сразу после окончания предыдущего.
Рекурсивный setTimeout — более гибкий метод тайминга, чем setInterval , так как время до следующего выполнения можно запланировать по-разному, в зависимости от результатов текущего.
Например, у нас есть сервис, который раз в 5 секунд опрашивает сервер на предмет новых данных. В случае, если сервер перегружен, можно увеличивать интервал опроса до 10, 20, 60 секунд. А потом вернуть обратно, когда всё нормализуется.
Если у нас регулярно проходят грузящие процессор задачи, то мы можем оценивать время, потраченное на их выполнение, и планировать следующий запуск раньше или позже.
Рекурсивный setTimeout гарантирует паузу между вызовами, setInterval — нет.
Давайте сравним два кода. Первый использует setInterval :
Второй использует рекурсивный setTimeout :
При setInterval внутренний таймер будет срабатывать чётко каждые 100 мс и вызывать func(i) :
Вы обратили внимание.
Реальная пауза между вызовами func при setInterval меньше, чем указана в коде!
Это естественно, ведь время работы функции никак не учитывается, оно «съедает» часть интервала.
Возможно и такое что func оказалась сложнее, чем мы рассчитывали и выполнялась дольше, чем 100 мс.
В этом случае интерпретатор будет ждать, пока функция завершится, затем проверит таймер и, если время вызова setInterval уже подошло (или прошло), то следующий вызов произойдёт сразу же.
Если функция и выполняется дольше, чем пауза setInterval , то вызовы будут происходить вообще без перерыва.
Исключением является IE, в котором таймер «застывает» во время выполнения JavaScript.
А так будет выглядеть картинка с рекурсивным setTimeout :

При рекурсивном setTimeout задержка всегда фиксирована и равна 100 мс.
Это происходит потому, что каждый новый запуск планируется только после окончания текущего.
Минимальная задержка таймера
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4 мс в современных браузерах. В более старых она может быть больше и достигать 15 мс.
По стандарту, минимальная задержка составляет 4 мс. Так что нет разницы между setTimeout(. 1) и setTimeout(. 4) .
Посмотреть минимальное разрешение «вживую» можно на следующем примере.
В примере ниже каждая полоска удлиняется вызовом setInterval с указанной на ней задержкой — от 0 мс (сверху) до 20 мс (внизу).
Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает.
[iframe border=»1″ src=»https://github.com/iliakan/javascript-tutorial-ru/blob/master/1-js/7-js-misc/3-settimeout-setinterval/setinterval-anim» link edit]
Реальная частота срабатывания
В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами setInterval(. 4) может быть не 4 мс, а 30 мс или даже 1000 мс.
Большинство браузеров (десктопных в первую очередь) продолжают выполнять setTimeout/setInterval , даже если вкладка неактивна.
При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в «фоновой» вкладке будет срабатывать таймер, но редко.
При работе от батареи, в ноутбуке — браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек.
При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски setInterval будут пропущены.
Вывод: на частоту 4 мс стоит ориентироваться, но не стоит рассчитывать.
Разбивка долгих скриптов
Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения «тяжелых» скриптов.
Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ — на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо.
Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время.
Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс — следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой — то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах.
- Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.
- Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval/clearTimeout .
- В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо setInterval используют рекурсивный setTimeout .
- Минимальная задержка по стандарту составляет 4 мс . Браузеры соблюдают этот стандарт, но некоторые другие среды для выполнения JS, например Node.JS, могут предоставить и меньше задержки.
- В реальности срабатывания таймера могут быть гораздо реже, чем назначено, например если процессор перегружен, вкладка находится в фоновом режиме, ноутбук работает от батареи или по какой-то иной причине.
Браузерных особенностей почти нет, разве что вызов setInterval(. 0) с нулевой задержкой в IE недопустим, нужно указывать setInterval(. 1) .
setTimeout и setInterval
Привет, сегодня поговорим про settimeout, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое settimeout, setinterval , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).
- setTimeout
- Параметры для функции и контекст
- Отмена исполнения
Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.
В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.
setTimeout
var timerId = setTimeout(func/code, delay[, arg1, arg2. ]) func/code Функция или строка кода для исполнения.
Строка поддерживается для совместимости, использовать ее не рекомендуется. delay Задержка в милисекундах, 1000 милисекунд равны 1 секунде. arg1 , arg2 … Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.Исполнение функции произойдет спустя время, указанное в параметре delay .
Например, следующий код вызовет alert(‘Привет’) через одну секунду:
1 function func() < 2 alert( ‘Привет’ ); 4 setTimeout(func, 1000); Если первый аргумент является строкой, то интерпретатор создает анонимную функцию из этой строки.
То есть такая запись работает точно так же:
1 setTimeout( «alert(‘Привет’)» , 1000); Использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.
Вместо них используйте анонимные функции:
1 setTimeout( function () < alert( 'Привет' ) >, 1000); Параметры для функции и контекст
Во всех современных браузерах, с учетом IE10, setTimeout позволяет указать параметры функции.
Пример ниже выведет «Привет, я Вася» везде, кроме IE9-:
1 function sayHi(who) < 2 alert( «Привет, я » + who); 5 setTimeout( sayHi , 1000, «Вася» ); …Однако, в большинстве случаев нам нужна поддержка старого IE, а он не позволяет указывать аргументы. Поэтому, для того, чтобы их передать, оборачивают вызов в анонимную функцию:
1 function sayHi(who) < 2 alert( «Привет, я » + who); 5 setTimeout( function () < sayHi( 'Вася' ) >, 1000); Вызов через setTimeout не передает контекст this .
В частности, вызов метода объекта через setTimeout сработает в глобальном контексте. Это может привести к некорректным результатам.
Например, вызовем user.sayHi() через одну секунду:
01 function User(id) < 02 this .id = id; 04 this .sayHi = function () < 05 alert( this .id); 09 var user = new User(12345); 11 setTimeout(user.sayHi, 1000); // ожидается 12345, но выведет «undefined» Так как setTimeout запустит функцию user.sayHi в глобальном контексте, она не будет иметь доступ к объекту через this .
Иначе говоря, эти два вызова setTimeout делают одно и то же:
1 // (1) одна строка 2 setTimeout(user.sayHi, 1000); 4 // (2) то же самое в две строки 5 var func = user.sayHi; 6 setTimeout(func, 1000); К счастью, эта проблема также легко решается созданием промежуточной функции:
01 function User(id) < 02 this .id = id; 04 this .sayHi = function () < 05 alert( this .id); 09 var user = new User(12345); 11 setTimeout( function () < 12 user.sayHi(); Функция-обертка используется, чтобы кросс-браузерно передать аргументы и сохранить контекст выполнения.
В следующих главах мы разберем дополнительные способы привязки функции к объекту.
Отмена исполнения
Функция setTimeout возвращает идентификатор timerId , который можно использовать для отмены действия.
В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.
1 var timerId = setTimeout( function () < alert(1) >, 1000); 3 clearTimeout(timerId); setInterval
Метод setInterval имеет синтаксис, аналогичный setTimeout .
var timerId = setInterval(func/code, delay[, arg1, arg2. ]) Смысл аргументов — тот же самый. Но, в отличие от setTimeout , он запускает выполнение функции не один раз, а регулярно повторяет ее через указанный интервал времени. Остановить исполнение можно вызовом clearInterval(timerId) .
Следующий пример при запуске станет выводить сообщение каждые две секунды, пока вы не нажмете на кнопку «Стоп»:
1 < input type = «button» onclick = «clearInterval(timer)» value = «Стоп» > 4 var i = 1; 5 var timer = setInterval( function () < alert(i++) >, 2000); Очередь и наложение вызовов в setInterval
Вызов setInterval(функция, задержка) ставит функцию на исполнение через указанный интервал времени. Но здесь есть тонкость.
На самом деле пауза между вызовами меньше, чем указанный интервал.
Для примера, возьмем setInterval(function() < func(i++) >, 100) . Она выполняет func каждые 100 мс, каждый раз увеличивая значение счетчика.
На картинке ниже, красный блок — это время исполнения func . Время между блоком — это время между запусками функции, и оно меньше, чем установленная задержка!

То есть, браузер инициирует запуск функции аккуратно каждые 100мс , без учета времени выполнения самой функции.
Бывает, что исполнение функции занимает больше времени, чем задержка. Например, функция сложная, а задержка маленькая. Или функция содержит операторы alert/confirm/prompt , которые блокируют поток выполнения. В этом случае начинаются интересные вещи
Если запуск функции невозможен, потому что браузер занят — она становится в очередь и выполнится, как только браузер освободится.
Изображение ниже иллюстрирует происходящее для функции, которая долго исполняется.
Вызов функции, инициированный setInterval , добавляется в очередь и незамедлительно происходит, когда это становится возможным:

Второй запуск функции происходит сразу же после окончания первого:

Больше одного раза в очередь выполнение не ставится.
Если выполнение функции занимает больше времени, чем несколько запланированных исполнений, то в очереди она все равно будет стоять один раз. Так что «накопления» запусков не происходит.
На изображении ниже setInterval пытается выполнить функцию в 200 мс и ставит вызов в очередь. В 300 мс и 400 мс таймер пробуждается снова, но ничего не просходит.

Давайте посмотрим на примере, как это работает.
Внутренний таймер в браузерах Safari/Chrome во время показа alert/confirm/prompt не «тикает». Если до исполнения оставалось 3 секунды, то даже при показе alert на протяжении минуты — задержка остается 3 секунды.
Поэтому пример ниже не воспроизводится в этих браузерах. В других браузерах все в порядке.
- Запустите пример ниже в любом браузере, кроме Chrome/Safari и дождитесь всплывающего окна. Обратите внимание, что это alert . Пока модальное окошко отображается, исполнение JavaScript блокируется. Подождите немного и нажмите OK.
- Вы должны увидеть, что второй запуск будет тут же, а третий — через небольшое время от второго, меньшее чем 2000 мс.
- Чтобы остановить повторение, нажмите кнопку Стоп .
1 < input type = «button» onclick = «clearInterval(timer)» value = «Стоп» > 4 var i = 1; 5 var timer = setInterval( function () < alert(i++) >, 2000); - Браузер выполняет функцию каждые 2 секунды
- Когда всплывает окно alert — исполнение блокируется и остается заблокированным все время, пока alert отображается.
- Если вы ждете достаточно долго, то внутренние часики-то идут. Браузер ставит следующее исполнение в очередь, один раз (в Chrome/Safari внутренний таймер не идет! это ошибка в браузере).
- Когда вы нажмете OK — моментально вызывается исполнение, которое было в очереди.
- Следующее исполнение вызовется с меньшей задержкой, чем указано. Так происходит потому, что планировщик просыпается каждые 2000мс. И если alert был закрыт в момент времени, соответствующий 3500мс от начала, то следующее исполнение назначено на 4000 мс, т.е. произойдет через 500мс.
Вызов setInterval(функция, задержка) не гарантирует реальной задержки междуисполнениями.
Бывают случаи, когда реальная задержка больше или меньше заданной. Вообще, не факт, что будет хоть какая-то задержка.
Напишите функцию, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать 2000мс, в течение которых каждые 100мс в консоли появляется очередное число.
Нажмите на кнопку, открыв консоль, для демонстрации:
Решение задачи должно использовать setInterval .
01 function printNumbersInterval20_100() < 02 var i = 1; 03 var timerId = setInterval( function () < 04 console.log(i); 05 if (i == 20) clearInterval(timerId); 11 printNumbersInterval20_100(); Повторение вложенным setTimeout
В случаях, когда нужно не просто регулярное повторение, а обязательна задержка между запусками, используется повторная установка setTimeout при каждом выполнении функции.
Ниже — пример, который выдает alert с интервалами 2 секунды между ними.
01 < input type = «button» onclick = «clearTimeout(timer)» value = «Стоп» > 04 var i = 1; 06 var timer = setTimeout( function run() < 08 timer = setTimeout(run, 2000); На временной линии выполнения будут фиксированные задержки между запусками . Об этом говорит сайт https://intellect.icu . Иллюстрация для задержки 100мс:

Сделайте то же самое, что в задаче Вывод чисел каждые 100мс, но с использованием setTimeout вместо setInterval .
01 function printNumbersTimeout20_100() < 02 var i = 1; 03 var timerId = setTimeout( function go() < 04 console.log(i); 05 if (i < 20) setTimeout(go, 100); 11 printNumbersTimeout20_100(); Минимальная задержка таймера
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.
По стандарту, минимальная задержка составляет 4мс. Так что нет разницы между setTimeout(. 1) и setTimeout(. 4) .
Посмотреть минимальное разрешение «вживую» можно на следующем примере.
В примере ниже находятся DIV’ы , каждый удлиняется вызовом setInterval с указанной в нем задержкой — от 0мс (сверху) до 20мс (внизу).
Запустите его в различных браузерах, в частности, в Chrome и Firefox. Вы наверняка заметите, что несколько первых DIV’ов анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает.
Открыть в новом окне Открыть в песочнице
В поведении setTimeout и setInterval с нулевой задержкой есть браузерные особенности.
- В Opera, setTimeout(. 0) — то же самое, что setTimeout(. 4) . Оно выполняется реже, чем setTimeout(.. ,2) . Это особенность данного браузера.
- В Internet Explorer, нулевая задержка setInterval(. 0) не сработает. Это касается именно setInterval , т.е. setTimeout(. 0) работает нормально.
Пример ниже реализует такую же анимацию, но через setTimeout . Если посмотреть его в различных браузерах, то можно заметить отличия от setInterval .
Открыть в новом окне Открыть в песочнице
Реальная частота срабатывания
В ряде случаев задержка может быть не 4мс, а 30мс или даже 1000мс.
-
Большинство браузеров (десктопных в первую очередь) продолжают выполнять setTimeout/setInterval , даже если вкладка неактивна.
При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в «фоновой» вкладке будет срабатывать таймер, но редко.
Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.
Посмотрим снижении частоты в действии на небольшом примере.
При клике на кнопку ниже запускается setInterval(. 90) , который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь.
Запустить повтор с интервалом в 90 мс
Остановить повторЕсли ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные красным.
Кроме того, вы точно увидите, что таймер не является идеально точным
Код, который используется в примере выше и считает интервалы времени между вызовами, выглядит примерно так:
01 var timeMark = new Date; 02 setTimeout( function go() < 03 var diff = new Date — timeMark; 05 // вывести очередную задержку в консоль вместо страницы 06 console.log(diff); 08 // запомним время в самом конце, 09 // чтобы измерить задержку именно между вызовами 10 timeMark = new Date; 12 setTimeout(go, 100); Разбивка долгих скриптов
Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения «тяжелых» скриптов.
Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ — на большом файле это займет много времени.
Браузер сначала будет есть 100% процессора, а затем может выдать сообщение о том, что скрипт выполняется слишком долго.
Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время. Например, планируется подсветка 20 строк каждые 10мс.
Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку.
Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10мс.
Как мы знаем, есть два варианта реализации такой подсветки:
-
Через setInterval , с остановкой по окончании работы:
1 timer = setInterval( function () < 2 if (есть еще что подсветить) highlight(); 3 else clearInterval(timer); 1 setTimeout( function go() < 2 highlight(); 3 if (есть еще что подсветить) setTimeout(go, 10); Какой из них стоит использовать? Почему?
Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями highlight .
Первый вариант может загрузить процессор на 100%, если highlight занимает время, близкое к 10мс или, тем более, большее чем 10мс, т.к. таймер не учитывает время выполнения функции.
Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2.
Трюк setTimeout(func, 0)
Этот трюк достоин войти в анналы JavaScript-хаков.
Функцию оборачивают в setTimeout(func, 0) , если хотят запустить ее после окончания текущего скрипта.
Дело в том, что setTimeout никогда не выполняет функцию сразу. Он лишь планирует ее выполнение. Но интерпретатор JavaScript начнет выполнять запланированные функции лишь после выполнения текущего скрипта.
По стандарту, setTimeout в любом случае не может выполнить функцию с задержкой 0 . Как мы говорили раньше, обычно задержка составит 4мс. Но главное здесь именно то, что выполнение в любом случае будет после выполнения текущего кода.
01 var result; 03 function showResult() < 04 alert(result); 07 setTimeout(showResult, 0); 09 result = 2*2; 11 // выведет 4 Позже, в главе Управление порядком обработки, setTimeout(…0), мы рассмотрим различные применения этого трюка при работе с событиями.
Итого
Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.
Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval/clearTimeout .
Время выполнения функции не учитывается, поэтому промежуток времени от окончания одного запуска до начала другого может быть различным.
Есть два бегуна:
var runner1 = new Runner(); var runner2 = new Runner(); У каждого есть метод step() , который делает шаг.
Какой из двух бегунов будет быстрее?
01 // первый? 02 setInterval( function () < 03 runner1.step(); 06 // или второй? 07 setTimeout( function go() < 08 runner2.step(); 09 setTimeout(go, 15); 12 setTimeout( function () < 13 alert(runner1.steps); 14 alert(runner2.steps); Кто сделает больше шагов? Почему вы так думаете?
Обычно первый бегун будет быстрее.
Но возможен и такой вариант, что время совпадает.
Создадим реальные объекты Runner и запустим их для проверки:
01 function Runner() < 02 this .steps = 0; 04 this .step = function () < 05 doSomethingHeavy(); 06 this .steps++; 09 function doSomethingHeavy() < 10 for ( var i=0; i<10000; i++) < 11 this [i] = this .step + i; 17 var runner1 = new Runner(); 18 var runner2 = new Runner(); 20 // запускаем бегунов 21 setInterval( function () < 22 runner1.step(); 25 setTimeout( function go() < 26 runner2.step(); 27 setTimeout(go, 15); 30 // кто сделает больше шагов? 31 setTimeout( function () < 32 alert(runner1.steps); 33 alert(runner2.steps); -
Если бы в шаге step() не было вызова doSomethingHeavy() , то количество шагов было бы равным, так как времени на такой шаг нужно очень мало.
Интерпретатор JavaScript старается максимально оптимизировать такие часто повторяющиеся «узкие места». В данном случае вызов step свелся бы к одной операции микропроцессора. Это пренебрежимо мало по сравнению с остальным кодом.
Ведь в setTimeout пауза 15 мс будет между шагами, а setInterval шагает равномерно, каждые 15 мс. Получается чаще.
Выполнение функции f занимает примерно 1 секунду.
Что выведет alert в коде ниже?
Когда сработает setTimeout ? Выберите нужный вариант:
- До выполнения f .
- Во время выполнения f .
- Сразу же по окончании f .
- Через 10мс после окончания f .
01 setTimeout( function () < 07 function f() < 08 // точное время выполнения не играет роли 09 // здесь оно заведомо больше задержки setTimeout 10 for (i=0; i<1e8; i++) f[i%10] = i; Вызов alert(i) в setTimeout выведет 100000000 , так как срабатывание будет гарантировано после окончания работы текущего кода.
Очередь до запланированных вызовов доходит всегда лишь после окончания текущего скрипта.
Можете проверить это запуском:
01 setTimeout( function () < 07 function f() < 08 // точное время выполнения не играет роли 09 // здесь оно заведомо больше задержки setTimeout 10 for (i=0; i<1e8; i++) f[i%10] = i; Ответ на второй вопрос: 3 (сразу после).
Вызов планируется на 10мс от времени вызова setTimeout , но функция выполняется больше, чем 10мс , поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же.
Выполнение функции f занимает примерно 1 секунду.
Что выведет alert в коде ниже?
Когда сработает setInterval ? Выберите нужный вариант:
- До выполнения f , во время и после, перемежаясь с выполнением f .
- Во время выполнения f , один раз.
- Во время выполнения f , возможно несколько раз.
- Сразу же по окончании f один раз.
- Сразу же по окончании f , возможно несколько раз.
- Через 10мс после окончания f , один раз.
- Через 10мс после окончания f , возможно несколько раз.
Является ли такое поведение кросс-браузерным?
01 var timer = setInterval( function () < 05 setTimeout( function () < 06 clearInterval(timer); 12 function f() < 13 // точное время выполнения не играет роли 14 // здесь оно заведомо больше 50мс 15 for (i=0; i<1e8; i++) f[i%10] = i; Вызов alert(i) в setTimeout введет 100000001 . Почему — будет понятно из ответа на второй вопрос.
Можете проверить это запуском:
01 var timer = setInterval( function () < 05 setTimeout( function () < 06 clearInterval(timer); 12 function f() < 13 // точное время выполнения не играет роли 14 // здесь оно заведомо больше 50мс 15 for (i=0; i<1e8; i++) f[i%10] = i; Ответ на второй вопрос: 4 (сразу же по окончании f один раз).
Планирование setInterval будет вызывать функцию каждые 10мс после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит.
За время выполнения f может пройти время, на которое запланированы несколько вызовов setInterval , но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы setInterval .
После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней setInterval и выполняет. А затем тут же выполняется setTimeout , очередь которого тут же подошла.
Итого, как раз и видим, что setInterval выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно.
Напишите функцию delay(f, ms) , которая возвращает обертку вокруг f , задерживающую вызов на ms миллисекунд.
1 function f(x) < 5 var f1000 = delay(f, 1000); 6 var f1500 = delay(f, 1500); 8 f1000( «тест» ); // выведет «тест» через 1000 миллисекунд 9 f1500( «тест2» ); // выведет «тест2» через 1500 миллисекунд Иначе говоря, f1000 — это «задержанный на 1000мс» вызов f .
В примере выше у функции только один аргумент, но delay должна быть универсальной: передавать любое количество аргументов и контекст this .
01 function delay(f, ms) < 03 return function () < 04 var savedThis = this ; 05 var savedArgs = arguments; 07 setTimeout( function () < 08 f.apply(savedThis, savedArgs); 14 function f(x) < 18 var f1000 = delay(f, 1000); 19 var f1500 = delay(f, 1500); 21 f1000( «тест» ); // выведет «тест» через 1000 миллисекунд 22 f1500( «тест2» ); // выведет «тест2» через 1500 миллисекунд Обратим внимание на то, как работает обертка:
1 return function () < 2 var savedThis = this ; 3 var savedArgs = arguments; 5 setTimeout( function () < 6 f.apply(savedThis , savedArgs); Именно обертка возвращается декоратором delay и будет вызвана. Чтобы передать аргумент и контекст функции, вызываемой через ms миллисекунд, они копируются в локальные переменные savedThis и savedArgs .
Это один из самых простых, и в то же время удобных способов передать что-либо в функцию, вызываемую через setTimeout .
Напишите функцию debounce(f, ms) , которая возвращает обертку, которая передает вызов f не чаще, чем раз в ms миллисекунд.
«Лишние» вызовы игнорируются. Все аргументы и контекст — передаются.
01 function f() 03 var f = debounce(f, 1000); 05 f(1); // выполнится сразу же 06 f(2); // игнор 08 setTimeout( function () < f(3) >, 100); // игнор (прошло только 100мс) 09 setTimeout( function () < f(4) >, 1100); // выполнится 10 setTimeout( function () < f(5) >, 1500); // игнор Исходный документ с более развернутым тестом: tutorial/timers/debounce-src.html
Вызов debounce возвращает функцию-обертку. Все необходимые данные для нее хранятся в замыкании.
При вызове ставится таймер и состояние state меняется на константу COOLDOWN («в процессе охлаждения»).
Последующие вызовы игнорируются, пока таймер не обнулит состояние.
Напишите функцию throttle(f, ms) — «тормозилку», которая возвращает обертку, передающую вызов f не чаще, чем раз в ms миллисекунд.
У этой функции должно быть важное существенное отличие от debounce : если игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет — то он выполнится.
Чтобы лучше понять, откуда взялось это требование, и как throttle должна работать — разберем реальное применение, на которое и ориентирована эта задача.
Например, нужно обрабатывать передвижения мыши. В JavaScript это делается функцией, которая будет запускаться при каждом микро-передвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс).
Функция обработки передвижения должна обновлять некую информацию на странице. При этом обновление — слишком «тяжелый» процесс, чтобы делать его при каждом микро-передвижении. Имеет смысл делать его раз в 100мс, не чаще.
Пусть функция, которая осуществляет это обновление по передвижению, называется onmousemove .
Вызов throttle(onmousemove, 100) , по сути, предназначен для того, чтобы «притормаживать» обработку onmousemove . Технически, он должен возвращать обертку, которая передает все вызовы onmousemove , но не чаще чем раз в 100мс.
При этом промежуточные движения можно игнорировать, но мышь в конце концов где-то остановится. И это последнее, итоговое положение мыши обязательно нужно обработать!
Визуально это даст следующую картину обработки перемещений мыши:
- Первое обновление произойдет сразу (это важно, посетитель тут же видит реакцию на свое действие).
- Дальше будет много вызовов (микро-передвижений) с разными координатами, но пока не пройдет 100мс — ничего не будет.
- По истечении 100мс — опять обновление, с последними координатами. Промежуточные микро-передвижения игнорированы.
- В конце концов мышь где-то остановится, обновление по окончании очередной паузы 100мс (иначе мы не знаем, последнее оно или нет) сработает с последними координатами.
Еще раз заметим — задача из реальной жизни, а в ней принципиально важно, чтопоследнее передвижение обрабатывается. Пользователь должен увидеть, где остановил мышь.
Чтобы было удобнее такой throttle писать, в исходном документе содержатся еще два теста: tutorial/timers/throttle-src.html
Вызов throttle возвращает функцию-обертку. Все необходимые данные для нее хранятся в замыкании.
В первую очередь — это состояние state , которое вначале не назначено ( null ), при первом вызове получает значение COOLDOWN («в процессе охлаждения»), а при следующем вызове CALL_SCHEDULED .
При таймауте состояние проверяется, и если оно равно CALL_SCHEDULED — происходит новый вызов.
Понравилась статья про settimeout? Откомментируйте её Надеюсь, что теперь ты понял что такое settimeout, setinterval и для чего все это нужно, а если не понял, или есть замечания, то нестесняся пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)