Дебаунс и троттлинг в JavaScript: понятное руководство с примерами

Когда пользователь печатает в поле ввода, скроллит страницу или меняет размер окна, обработчики событий могут вызываться десятки раз в секунду. Без ограничений это приводит к лагам, лишним запросам и «тяжелому» интерфейсу. На помощь приходят два приема: дебаунс (debounce) и троттлинг (throttle).
Что такое дебаунс и троттлинг
- Дебаунс — запускает функцию только после того, как поток событий закончился и прошло заданное время ожидания. Полезно для автопоиска, валидации, автосохранения.
- Троттлинг — ограничивает частоту вызовов, гарантируя, что функция не выполнится чаще заданного интервала. Идеален для scroll, resize, drag, mousemove.
Дебаунс: реализация и применение
function debounce(fn, wait = 300, { leading = false, trailing = true } = {}) {
let timer = null;
let lastArgs, lastThis, result;
const invoke = () => {
timer = null;
if (trailing && lastArgs) {
result = fn.apply(lastThis, lastArgs);
lastArgs = lastThis = null;
}
};
function debounced(...args) {
lastArgs = args;
lastThis = this;
if (timer) clearTimeout(timer);
if (leading && !timer) {
result = fn.apply(lastThis, lastArgs);
lastArgs = lastThis = null;
}
timer = setTimeout(invoke, wait);
return result;
}
debounced.cancel = () => {
if (timer) clearTimeout(timer);
timer = null;
lastArgs = lastThis = null;
};
debounced.flush = () => {
if (timer) {
clearTimeout(timer);
invoke();
}
return result;
};
return debounced;
}
Пример: автопоиск по мере ввода
const input = document.querySelector('#search');
const fetchResults = async (q) => {
if (!q) return [];
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
return res.json();
};
const onSearch = debounce(async (q) => {
const data = await fetchResults(q);
console.log('Результаты:', data);
}, 400);
input.addEventListener('input', (e) => onSearch(e.target.value));
Советы по дебаунсу:
- Для «живого поиска» чаще всего подходят параметры:
wait = 300–500
мс,leading = false
,trailing = true
. - Для мгновенного отклика при первом вводе можно включить
leading: true
и оставитьtrailing: true
для финального запроса. - При смене страницы/компонента вызывайте
debounced.cancel()
для очистки таймера.
Троттлинг: реализация и применение
function throttle(fn, wait = 200, { leading = true, trailing = true } = {}) {
let lastCall = 0;
let timer = null;
let lastArgs, lastThis, result;
const invoke = (time) => {
lastCall = time;
result = fn.apply(lastThis, lastArgs);
lastArgs = lastThis = null;
};
function throttled(...args) {
const now = Date.now();
if (!lastCall && leading === false) lastCall = now;
const remaining = wait - (now - lastCall);
lastArgs = args;
lastThis = this;
if (remaining <= 0 || remaining > wait) {
if (timer) { clearTimeout(timer); timer = null; }
invoke(now);
} else if (trailing !== false && !timer) {
timer = setTimeout(() => {
timer = null;
invoke(Date.now());
}, remaining);
}
return result;
}
throttled.cancel = () => {
if (timer) clearTimeout(timer);
timer = null;
lastCall = 0;
lastArgs = lastThis = null;
};
return throttled;
}
Пример: оптимизация скролла
const progress = document.querySelector('#progress');
function updateProgress() {
const max = document.documentElement.scrollHeight - window.innerHeight;
const value = (window.scrollY / max) * 100;
progress.style.width = `${Math.min(100, Math.max(0, value))}%`;
}
const onScroll = throttle(updateProgress, 100, { leading: true, trailing: true });
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('load', updateProgress);
Советы по троттлингу:
- Для scroll/resize используйте
{ passive: true }
в addEventListener, чтобы ускорить прокрутку. - Интервал 50–200 мс обычно дает хороший баланс между плавностью и нагрузкой.
- Если важно мгновенно реагировать, включайте
leading: true
.
Debounce vs Throttle: когда что выбрать
- Debounce — когда важен только итоговый, «финальный» вызов после серии событий: поиск, автосохранение, валидация формы.
- Throttle — когда нужно ограничить частоту: визуальные эффекты при скролле, пауза между drag-событиями, обработка resize.
Типичные ошибки и как их избежать
- Потеря контекста this. Если оборачиваете метод объекта, не используйте стрелочную функцию при объявлении метода, либо заранее
bind
ьте:obj.method = debounce(obj.method.bind(obj), 300)
. - Не снимаете обработчики. При уничтожении виджетов/компонентов вызывайте
cancel()
иremoveEventListener
. - Слишком большой интервал. Слишком “жесткий” троттлинг делает интерфейс «дерганым». Подберите значение на практике.
- Игнорирование rAF. Для чисто визуальных обновлений попробуйте
requestAnimationFrame
.
Альтернатива с requestAnimationFrame для скролла
let ticking = false;
function onScrollRaf() {
if (!ticking) {
window.requestAnimationFrame(() => {
updateProgress();
ticking = false;
});
ticking = true;
}
}
window.addEventListener('scroll', onScrollRaf, { passive: true });
Готовые решения из экосистемы
Если не хочется писать свои утилиты, используйте battle-tested реализации из lodash:
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
const onInput = debounce(handleChange, 300, { leading: false, trailing: true });
const onMove = throttle(handleMove, 100, { leading: true, trailing: true });
Lodash корректно обрабатывает тонкости, имеет методы cancel
и flush
, а также стабильные API, что уменьшает риск багов в продакшене.
Итоги
Дебаунс и троттлинг — простые, но крайне эффективные инструменты повышения производительности фронтенда. Освойте их — и ваши интерфейсы станут быстрее и стабильнее. Хотите системно прокачать JavaScript с практикой и проектами? Загляните сюда: Пройти практический курс «JavaScript с Нуля до Гуру 2.0» и посмотреть программу.
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.