Как работает SetTimeout?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает setTimeout
setTimeout — это один из самых основных инструментов в JavaScript для отложенного выполнения кода. Хотя на первый взгляд это просто, под капотом происходит много интересного.
Базовый синтаксис
setTimeout(callback, delay, ...args);
// Примеры:
setTimeout(() => {
console.log("Привет!");
}, 1000); // выполнится через 1000мс (1 секунду)
setTimeout(() => {
console.log("Hello");
}); // выполнится как можно скорее (delay = 0)
Важная концепция: Delay — это МИНИМУМ, не максимум
setTimeout гарантирует, что callback не выполнится ДО истечения задержки, но НЕ гарантирует, что он выполнится ровно в это время.
const start = Date.now();
setTimeout(() => {
const elapsed = Date.now() - start;
console.log(`Прошло ${elapsed}мс`); // может быть 1005, 1010, 1050мс
}, 1000);
// Если браузер занят, callback будет отложен дольше
for (let i = 0; i < 1000000000; i++) {} // тяжелое вычисление
// callback выполнится ПОСЛЕ завершения цикла
Event Loop и Macrotask Queue
setTimeout — это макротаск (macrotask). Вот как это работает:
console.log("1. Начало");
setTimeout(() => {
console.log("3. setTimeout callback");
}, 0);
Promise.resolve()
.then(() => {
console.log("2. Promise микротаск");
});
console.log("4. Конец синхронного кода");
// Порядок вывода:
// 1. Начало
// 4. Конец синхронного кода
// 2. Promise микротаск
// 3. setTimeout callback
Почему такой порядок?
- Выполняется весь синхронный код (1 и 4)
- Затем выполняются все микротаски из очереди (Promise, 2)
- Затем выполняется первый макротаск из очереди (setTimeout, 3)
Визуализация Event Loop
// Call Stack → Microtask Queue → Macrotask Queue
console.log("start"); // Call Stack
setTimeout(() => { // Добавляется в Macrotask Queue
console.log("timeout");
}, 0);
Promise.resolve()
.then(() => { // Добавляется в Microtask Queue
console.log("promise");
});
console.log("end"); // Call Stack
// Вывод:
// start
// end
// promise
// timeout
setTimeout с delay = 0
Много разработчиков думают, что setTimeout(fn, 0) выполнится сразу, но это не так.
console.log("Начало");
setTimeout(() => {
console.log("Это НЕ выполнится сразу!");
}, 0); // даже с 0мс задержки, callback пойдёт в Macrotask Queue
console.log("Конец");
// Вывод:
// Начало
// Конец
// Это НЕ выполнится сразу!
Минимальная задержка в браузерах
У браузеров есть минимальная задержка для setTimeout. Если указать меньше, браузер всё равно установит минимальное значение.
setTimeout(() => {
console.log("Выполнится не раньше чем через 4мс");
}, 0); // даже если написать 0
setTimeout(() => {
console.log("Выполнится не раньше чем через 4мс");
}, 1);
setTimeout(() => {
console.log("Выполнится не раньше чем через 4мс");
}, 2);
setTimeout(() => {
console.log("Это выполнится примерно через 4-5мс");
}, 4);
// После 5 уровней вложенности setTimeout, минимальная задержка = 4мс
Это называется "throttling setTimeout" и сделано для экономии ресурсов.
Практические примеры
Пример 1: Батчинг обновлений
let updates = [];
let isScheduled = false;
function scheduleUpdate(value) {
updates.push(value);
if (!isScheduled) {
isScheduled = true;
setTimeout(() => {
console.log("Обработка", updates);
updates = [];
isScheduled = false;
}, 0); // Потом обработаем все накопленные обновления
}
}
scheduleUpdate(1);
scheduleUpdate(2);
scheduleUpdate(3);
// Выведет: "Обработка [1, 2, 3]" одним батчем
Пример 2: UI обновления
// Плохо — блокирует UI
function slowOperation() {
for (let i = 0; i < 1000000; i++) {
document.getElementById('result').textContent = i;
}
}
// Хорошо — делим работу на части
function fastOperation(data, index = 0) {
if (index < data.length) {
document.getElementById('result').textContent = data[index];
setTimeout(() => fastOperation(data, index + 1), 0);
}
}
Пример 3: Дебаунсинг
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
const handleSearch = debounce((query) => {
console.log("Поиск:", query);
}, 300);
handleSearch("a");
handleSearch("ab");
handleSearch("abc"); // Выполнится только это
Return value: ID таймера
setTimeout возвращает ID, который можно использовать для отмены:
const timerId = setTimeout(() => {
console.log("Этого не будет");
}, 1000);
// Отменяем выполнение
clearTimeout(timerId);
// callback никогда не выполнится
Передача аргументов
function greet(name, greeting) {
console.log(`${greeting}, ${name}!`);
}
// До ES5
setTimeout(function() {
greet("Alice", "Hello");
}, 1000);
// ES5+ — передаём аргументы третьим параметром
setTimeout(greet, 1000, "Alice", "Hello");
// Выведет: "Hello, Alice!" через 1 сек
Частые ошибки
Ошибка 1: Потеря контекста this
const obj = {
name: "Alice",
sayHi() {
setTimeout(function() {
console.log(this.name); // undefined, потеря this!
}, 1000);
}
};
obj.sayHi();
// Решение 1: arrow function
const obj = {
name: "Alice",
sayHi() {
setTimeout(() => {
console.log(this.name); // "Alice"
}, 1000);
}
};
// Решение 2: bind
setTimeout(function() {
console.log(this.name);
}.bind(obj), 1000);
Ошибка 2: Неправильное понимание задержки
// Новичок думает: "Выполнится через 1000мс"
// На самом деле: "Добавится в очередь, выполнится не раньше чем через 1000мс"
setTimeout(() => {
console.log("Может быть через 1005мс, если браузер занят");
}, 1000);
setTimeout vs setInterval vs requestAnimationFrame
// setTimeout — одноразовое выполнение
setTimeout(() => console.log("Once"), 1000);
// setInterval — повторное выполнение
const id = setInterval(() => console.log("Every 1000ms"), 1000);
clearInterval(id); // остановить
// requestAnimationFrame — синхронизация с обновлением экрана
requestAnimationFrame(() => {
console.log("Выполнится перед следующей перерисовкой (~60fps = ~16.67ms)");
});
Резюме
- setTimeout добавляет callback в Macrotask Queue
- Callback выполнится НЕ РАНЬШЕ чем через указанную задержку
- Все микротаски (Promise) выполняются ДО макротасков (setTimeout)
- setTimeout с delay=0 — это способ отложить выполнение на следующий цикл Event Loop
- Браузеры имеют минимальную задержку (~4мс после 5 уровней вложенности)
- Используй clearTimeout() чтобы отменить уже запланированный callback
- Будь осторожен с потерей контекста this при использовании обычных функций