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-handlerconst 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 fpswindow.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 и throttleimport { debounce } from 'lodash-es';const save = debounce(persist, 1000, { leading: false, trailing: true, maxWait: 5000 });// Отмена и немедленный вызов:save.cancel(); // отменить pending вызовsave.flush(); // вызвать немедленноИтог: Debounce — для событий, где важен итоговый результат после паузы; throttle — для непрерывного потока с нужной регулярностью обновлений.