W3docs

JavaScript setTimeout и setInterval

Узнайте, как планировать выполнение кода в JavaScript с помощью setTimeout и setInterval: синтаксис, передача аргументов, отмена таймеров, рекурсивный setTimeout, нулевая задержка, debouncing и throttling.

Иногда не нужно выполнять код прямо сейчас — нужно запустить его позже или запускать многократно. Две функции планирования JavaScript, setTimeout() и setInterval(), позволяют сделать именно это. В этом руководстве рассматриваются их синтаксис, способы передачи аргументов, отмена запланированного таймера, важный паттерн «рекурсивный setTimeout», неочевидное поведение нулевой задержки, а также два практических применения: debouncing и throttling.

Ни одна из этих функций не является частью базового языка JavaScript — они предоставляются средой выполнения (браузерами и Node.js). Описанное здесь поведение одинаково в обоих случаях, с несколькими отмеченными различиями.

Введение в функции таймингов JavaScript

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

Функция setTimeout()

setTimeout() выполняет функцию один раз после указанной задержки. Принимает функцию для выполнения и задержку в миллисекундах перед этим выполнением.

Синтаксис

let timerId = setTimeout(func, delay, arg1, arg2, ...);
  • func — функция (или, реже, строка кода) для выполнения.
  • delay — время ожидания в миллисекундах перед запуском. По умолчанию равно 0.
  • arg1, arg2, ... — необязательные аргументы, которые передаются непосредственно в func.

Возвращаемое значение — числовой идентификатор таймера, который позже можно передать в clearTimeout().

Пример

javascript— editable

Передача аргументов в колбэк

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

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

Функция setInterval()

setInterval() имеет ту же сигнатуру, что и setTimeout(), но вместо однократного запуска колбэка выполняет его многократно каждые delay миллисекунд, пока вы его не остановите.

Синтаксис

let timerId = setInterval(func, delay, arg1, arg2, ...);

Пример

javascript— editable
Информация
Если колбэк выполняется дольше, чем интервал, вызовы могут накапливаться и реальный промежуток между ними смещается. Браузер также гарантирует минимальный промежуток между колбэками, поэтому последовательные выполнения могут оказаться ближе к следующему тику, чем ожидается. Когда важна точная периодичность, предпочтительнее использовать рекурсивный паттерн setTimeout, описанный ниже.

Рекурсивный setTimeout против setInterval

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

javascript— editable

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

Обе функции возвращают идентификатор таймера. Сохранив этот идентификатор, можно отменить запланированную работу с помощью clearTimeout() или clearInterval(). (Обе функции очистки фактически взаимозаменяемы в большинстве движков, но сопоставление их с соответствующим планировщиком делает код читаемее.)

Остановка setTimeout()

Чтобы отменить таймаут, сохраните идентификатор, возвращённый setTimeout(), и передайте его в clearTimeout() до истечения задержки.

Пример

javascript— editable

Остановка setInterval()

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

Пример

javascript— editable

setTimeout с нулевой задержкой

setTimeout(func, 0) не выполняет func немедленно. Он планирует выполнение func сразу после того, как текущий синхронный код завершится. Это удобный способ «уступить управление» — дать браузеру перерисовать страницу или разбить длинную задачу на части — и он объясняет порядок вывода, который часто удивляет новичков:

javascript— editable

Обратите внимание, что таймеры — это макрозадачи: они выполняются после всех поставленных в очередь микрозадач (например, колбэков выполненных промисов). Точные правила порядка описаны в разделе Событийный цикл: микрозадачи и макрозадачи.

Практическое применение и советы

Два наиболее распространённых реальных применения setTimeout() — это debouncing и throttling — оба являются способами ограничить частоту вызова функции в ответ на быстро следующие друг за другом события.

Debouncing с помощью setTimeout()

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounced Input Example</title>
<script>
    // Debounce function to limit the rate at which a function is executed
    function debounce(func, wait) {
        let timeout;

        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };

            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Function to be debounced
    function fetchData(input) {
        alert(`API call with input: ${input}`); // Placeholder for an API call
    }

    // Create a debounced version of fetchData
    const debouncedFetchData = debounce(fetchData, 300);

    // Add the debounced function to an event listener
    function setup() {
        document.getElementById('searchInput').addEventListener('input', (event) => {
            debouncedFetchData(event.target.value);
        });
    }

    // Ensure setup is called once the document is fully loaded
    document.addEventListener('DOMContentLoaded', setup);
</script>
</head>
<body>
    <h3>Type in the input field:</h3>
    <input type="text" id="searchInput" placeholder="Start typing..." />
</body>
</html>

Throttling с помощью setTimeout()

Throttling выполняет функцию не чаще одного раза в limit миллисекунд, независимо от количества событий между ними. Если debouncing ждёт тишины, throttling гарантирует равномерный ритм — идеально для обработчиков scroll, resize или mousemove, которые иначе срабатывали бы десятки раз в секунду. Пример ниже использует подход leading-edge (выполняется сразу при первом событии, затем соблюдает паузу):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Throttled Scroll Event</title>
<style>
  /* Simple styling for demonstration */
  body, html {
    height: 200%; /* Make the page scrollable */
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
  }
  #log {
    position: fixed;
    top: 0;
    left: 0;
    background: white;
    border: 1px solid #ccc;
    padding: 10px;
    width: 300px;
  }
</style>
</head>
<body>
<div id="log">Scroll to see the effect...</div>
<script>
// Throttle function using setTimeout
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, Math.max(0, limit - (Date.now() - lastRan)));
    }
  }
}

// Function to be throttled
function handleScroll() {
  const log = document.getElementById('log');
  log.textContent = `Scroll event triggered at: ${new Date().toLocaleTimeString()}`;
}

// Add event listener for scroll
window.addEventListener('scroll', throttle(handleScroll, 1000));
</script>
</body>
</html>

Важные нюансы

  • Задержки — это минимум, а не гарантия. Если стек вызовов занят или событийный цикл перегружен, колбэк ждёт. Число, которое вы передаёте, — это самое раннее время запуска, а не обещание.
  • Фоновые вкладки имеют ограничения. Большинство браузеров замедляют таймеры в неактивных вкладках примерно до одного раза в секунду для экономии энергии, поэтому анимации и опросы замедляются, когда вкладка скрыта.
  • Вложенные таймауты ограничены ~4мс. После пяти вложенных вызовов setTimeout() браузеры устанавливают минимальную задержку около 4 миллисекунд, поэтому задержка 0 никогда не является по-настоящему нулевой в глубоких цепочках.
  • Максимальная задержка. Задержка, превышающая 2147483647 (2^31 − 1), вызывает переполнение 32-битного поля и воспринимается как 0, что приводит к немедленному срабатыванию вместо запуска в далёком будущем.
  • Привязка this. При передаче метода в виде setTimeout(obj.method, 1000) теряется this. Используйте стрелочную функцию — setTimeout(() => obj.method(), 1000) — или obj.method.bind(obj).
  • Всегда выполняйте очистку. Очищайте интервалы (и ожидающие таймауты), когда компонент размонтируется или работа больше не нужна, иначе произойдут утечки таймеров и вы можете работать с устаревшим состоянием.

Связанные темы

Заключение

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

Практика

Практика
Какие из следующих утверждений верны относительно использования `setTimeout()` и `setInterval()` в JavaScript?
Какие из следующих утверждений верны относительно использования `setTimeout()` и `setInterval()` в JavaScript?
Was this page helpful?