Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

setTimeout и setInterval

Язык JavaScript (ES5)Асинхронное программированиеПланирование задач

Основная идея

setTimeout и setInterval — встроенные функции JavaScript для планирования выполнения кода через определённый промежуток времени. setTimeout выполняет код один раз, setInterval — многократно с заданным интервалом.

Ключевые аспекты

  • setTimeout(fn, delay) — вызывает функцию один раз через delay миллисекунд
  • setInterval(fn, delay) — вызывает функцию повторно каждые delay миллисекунд
  • Возвращаемое значение — числовой идентификатор таймера для отмены
  • Минимальная задержка — браузеры ограничивают минимум ~4мс для вложенных таймеров
  • Не гарантированная точность — задержка может быть больше из-за загруженности Event Loop

Плюсы

  • Простой и понятный API
  • Встроены в браузер и Node.js
  • Позволяют создавать анимации и периодические задачи
  • Не блокируют основной поток выполнения

Минусы

  • Неточное время срабатывания при высокой нагрузке
  • setInterval может накапливать вызовы при долгих операциях
  • Забытые интервалы приводят к утечкам памяти
  • Не работают в фоновых вкладках браузера как ожидается

Частые ошибки на собеседованиях

  • Путают порядок аргументов: сначала функция, потом задержка
  • Забывают сохранить ID таймера для последующей отмены
  • Не понимают, что задержка 0 не означает мгновенное выполнение
  • Используют setInterval для анимаций вместо requestAnimationFrame

Введение и проблематика

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

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

setTimeout и setInterval не являются частью спецификации ECMAScript. Это Web API, предоставляемые браузером или средой выполнения (Node.js).


Базовая теория

setTimeout — отложенный вызов

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

Синтаксис:

js
const timerId = setTimeout(func, delay, arg1, arg2, ...);
ПараметрОписание
funcФункция для выполнения
delayЗадержка в миллисекундах (по умолчанию 0)
arg1, arg2, ...Аргументы, передаваемые в функцию

Пример:

Code Example 1: Что будет выведено в консоль и когда?

js
// Простой вызов через 2 секунды
setTimeout(() => {
  console.log('Прошло 2 секунды');
}, 2000);
 
// С передачей аргументов
function greet(name, greeting) {
  console.log(`${greeting}, ${name}!`);
}
 
setTimeout(greet, 1000, 'Анна', 'Привет');
// Через 1 секунду выведет: "Привет, Анна!"

setInterval — периодический вызов

setInterval планирует регулярное выполнение функции с указанным интервалом.

Синтаксис:

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

Пример:

Code Example 2: Что делает этот код? Как его остановить?

js
// Выводим текущее время каждую секунду
const timerId = setInterval(() => {
  const now = new Date();
  console.log(now.toLocaleTimeString());
}, 1000);

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

Отмена таймеров

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

js
// Отмена setTimeout
const timeoutId = setTimeout(() => {
  console.log('Это сообщение не появится');
}, 5000);
 
clearTimeout(timeoutId); // Отменяем до срабатывания

Code Example 3: Сколько раз выведется сообщение "Счётчик: N"? Почему интервал остановится?

js
// Отмена setInterval
let counter = 0;
const intervalId = setInterval(() => {
  counter++;
  console.log(`Счётчик: ${counter}`);
 
  if (counter >= 5) {
    clearInterval(intervalId); // Останавливаем после 5 итераций
    console.log('Интервал остановлен');
  }
}, 1000);
⚠️

Всегда сохраняйте ID таймера, если планируете его отменять. Забытые интервалы — частая причина утечек памяти.

Вложенный setTimeout vs setInterval

Для периодических задач можно использовать два подхода:

Code Example 4: В чём отличие между этими двумя подходами? Когда лучше использовать каждый?

js
// setInterval — фиксированный интервал между НАЧАЛОМ вызовов
setInterval(() => {
  // Если операция занимает 100мс,
  // а интервал 1000мс,
  // следующий вызов будет через 900мс после завершения
  doSomething();
}, 1000);

Важное отличие: вложенный setTimeout гарантирует паузу между выполнениями, а setInterval — нет. Если функция выполняется дольше интервала, вызовы setInterval могут накапливаться.

js
// Проблема с setInterval при долгих операциях
setInterval(() => {
  // Если эта операция занимает 1500мс,
  // а интервал 1000мс — вызовы начнут "накапливаться"
  heavyOperation();
}, 1000);
 
// Решение — вложенный setTimeout
function safeInterval() {
  heavyOperation();
  setTimeout(safeInterval, 1000); // Ждём 1с ПОСЛЕ завершения
}
setTimeout(safeInterval, 1000);

Пограничные кейсы

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

setTimeout(fn, 0) не выполняет функцию мгновенно. Код ставится в очередь макрозадач и выполняется после текущего синхронного кода:

Code Example 5: В каком порядке выведутся сообщения? Почему?

js
console.log('1: Начало');
 
setTimeout(() => {
  console.log('3: setTimeout с задержкой 0');
}, 0);
 
console.log('2: Конец');
 
// Вывод:
// 1: Начало
// 2: Конец
// 3: setTimeout с задержкой 0

Минимальная задержка в браузерах составляет около 4мс для вложенных таймеров (более 5 уровней вложенности). Это ограничение HTML5-спецификации.

Потеря контекста this

Code Example 6: Какая проблема в первом вызове setTimeout? Как её решить?

js
const user = {
  name: 'Иван',
  sayHi() {
    console.log(`Привет, ${this.name}!`);
  }
};
 
// Ошибка — this будет undefined или window
setTimeout(user.sayHi, 1000); // "Привет, undefined!"
 
// Решение 1: стрелочная функция
setTimeout(() => user.sayHi(), 1000);
 
// Решение 2: bind
setTimeout(user.sayHi.bind(user), 1000);

Таймеры в фоновых вкладках

Браузеры ограничивают частоту таймеров в неактивных вкладках для экономии ресурсов:

js
// В фоновой вкладке интервал может быть принудительно
// увеличен до 1000мс и более
setInterval(() => {
  // Этот код может выполняться реже, чем ожидается
  updateUI();
}, 100);

Плюсы и минусы

АспектsetTimeoutsetInterval
ИспользованиеОднократные задачиПовторяющиеся задачи
КонтрольЛегко отменитьНужно помнить об отмене
Накопление вызововНе проблемаМожет накапливаться
ГибкостьВысокая (динамическая задержка)Фиксированный интервал

Вопросы интервьюера

Q: Чем setTimeout с задержкой 0 отличается от немедленного вызова функции?

setTimeout(fn, 0) откладывает выполнение до следующей итерации Event Loop. Это позволяет браузеру обновить UI и обработать другие события перед выполнением функции.

Q: Почему setInterval может быть неточным?

Браузер не гарантирует точное время. Если Event Loop занят, вызов откладывается. Также браузеры ограничивают частоту в фоновых вкладках.

Q: Как реализовать точный интервал для анимаций?

Для анимаций лучше использовать requestAnimationFrame — он синхронизирован с частотой обновления экрана (обычно 60fps).

Q: Можно ли передать строку вместо функции в setTimeout?

Технически да (setTimeout("alert('hi')", 100)), но это плохая практика — работает как eval() и создаёт уязвимости безопасности.


Источники

Code Example 1: setTimeout with arguments

❓ Что будет выведено в консоль и когда?

js
setTimeout(() => {
  console.log('Прошло 2 секунды');
}, 2000);
 
function greet(name, greeting) {
  console.log(`${greeting}, ${name}!`);
}
 
setTimeout(greet, 1000, 'Анна', 'Привет');

Code Example 2: setInterval

❓ Что делает этот код? Как его остановить?

js
const timerId = setInterval(() => {
  const now = new Date();
  console.log(now.toLocaleTimeString());
}, 1000);

Code Example 3: Timer cancellation

❓ Сколько раз выведется сообщение "Счётчик: N"? Почему интервал остановится?

js
let counter = 0;
const intervalId = setInterval(() => {
  counter++;
  console.log(`Счётчик: ${counter}`);
 
  if (counter >= 5) {
    clearInterval(intervalId);
    console.log('Интервал остановлен');
  }
}, 1000);

Code Example 4: setInterval vs nested setTimeout

❓ В чём отличие между этими двумя подходами? Когда лучше использовать каждый?

Version A:

js
setInterval(() => {
  doSomething();
}, 1000);

Version B:

js
function tick() {
  doSomething();
  setTimeout(tick, 1000);
}
 
setTimeout(tick, 1000);

Code Example 5: Zero delay

❓ В каком порядке выведутся сообщения? Почему?

js
console.log('1: Начало');
 
setTimeout(() => {
  console.log('3: setTimeout с задержкой 0');
}, 0);
 
console.log('2: Конец');

Code Example 6: This context loss

❓ Какая проблема в первом вызове setTimeout? Как её решить?

js
const user = {
  name: 'Иван',
  sayHi() {
    console.log(`Привет, ${this.name}!`);
  }
};
 
setTimeout(user.sayHi, 1000);
 
setTimeout(() => user.sayHi(), 1000);
 
setTimeout(user.sayHi.bind(user), 1000);