Перейти к содержимому

Debounce vs Throttle

Debounce откладывает выполнение до окончания серии событий; throttle ограничивает частоту до одного вызова за период — оба паттерна критичны для производительности UI; на собеседованиях просят написать реализацию на месте и объяснить, когда что применять.

// Debounce: вызов происходит через delay мс ПОСЛЕ последнего триггера
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
timer = undefined;
fn.apply(this, args);
}, delay);
};
}
// Использование: search-input, autosave, resize-handler
const handleSearch = debounce(async query => {
const results = await apiFetch(`/search?q=${encodeURIComponent(query)}`);
renderResults(results);
}, 300);
input.addEventListener('input', e => handleSearch(e.target.value));
// Leading debounce: немедленный вызов, затем silence-период
function debounceLeading(fn, delay) {
let timer;
return function (...args) {
if (!timer) fn.apply(this, args); // первый вызов — немедленно
clearTimeout(timer);
timer = setTimeout(() => { timer = undefined; }, delay);
};
}
// Пример: защита кнопки от двойного нажатия — срабатывает сразу, игнорирует повторы
// Throttle: максимум один вызов за period мс
function throttle(fn, period) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= period) {
last = now;
return fn.apply(this, args);
}
};
}
// rAF-throttle: один вызов за frame (синхронизация с 60fps)
function rafThrottle(fn) {
let raf = null;
return function (...args) {
if (raf !== null) return;
raf = requestAnimationFrame(() => {
fn.apply(this, args);
raf = null;
});
};
}
// Использование
const onScroll = throttle(() => {
updateProgressBar(window.scrollY / document.body.scrollHeight);
}, 100); // не чаще 10 fps
window.addEventListener('scroll', onScroll, { passive: true });
const onMouseMove = rafThrottle(e => drawCursor(e.clientX, e.clientY));
canvas.addEventListener('mousemove', onMouseMove);
// Когда что использовать
//
// Use case Pattern Причина
// ───────────────── ───────────────── ──────────────────────────────
// Search input debounce 200-400ms запрос после паузы в наборе
// Autosave в редакторе debounce 1000ms не сохранять каждый символ
// Кнопка отправки debounce leading мгновенно + блок повторов
// Window resize debounce 150ms пересчёт layout после конца
// Scroll progress throttle 100ms регулярные обновления
// mousemove / drag rafThrottle синхронно с framerate
// Keyboard shortcuts debounce leading мгновенная реакция + защита
// Lodash: _.debounce(fn, ms, { leading, trailing, maxWait })
// maxWait гарантирует вызов не позже maxWait мс — нечто среднее между debounce и throttle
import { debounce } from 'lodash-es';
const save = debounce(persist, 1000, { leading: false, trailing: true, maxWait: 5000 });
// Отмена и немедленный вызов:
save.cancel(); // отменить pending вызов
save.flush(); // вызвать немедленно

Итог: Debounce — для событий, где важен итоговый результат после паузы; throttle — для непрерывного потока с нужной регулярностью обновлений.