Debounce и throttle: идея и реализация

Два приёма контроля частоты вызовов — обязательная практическая задача на собеседовании.

Debounce откладывает вызов, пока события не прекратятся. Throttle разрешает вызов не чаще раза в заданный интервал.

Зачем это нужно

Некоторые события случаются очень часто: ввод в поле, скролл, изменение размера окна. Если на каждое из них дёргать тяжёлый обработчик (запрос на сервер, перерисовку) — интерфейс начнёт тормозить. debounce и throttle ограничивают частоту вызовов.

ПриёмПоведениеПример
debounceждёт паузу, потом один вызовпоиск по мере ввода
throttleне чаще раза в N мсобработка скролла

Реализация debounce

Идея: при каждом вызове сбрасываем предыдущий таймер и ставим новый. Реальный вызов происходит, только когда между событиями прошла достаточная пауза. Замыкание хранит timer между вызовами.

function debounce(fn, delay) {
  let timer = null;           // замыкание помнит таймер
  return function (...args) {
    clearTimeout(timer);      // отменяем прошлый
    timer = setTimeout(() => fn(...args), delay);
  };
}

let calls = 0;
const search = debounce(() => { calls++; }, 100);

search(); search(); search(); // быстрые подряд — должен сработать один раз

setTimeout(() => console.log("вызовов реально:", calls), 200);

Вывод:

вызовов реально: 1

Три быстрых вызова подряд схлопнулись в один: каждый новый сбрасывал таймер предыдущего, и сработал только последний, когда поток вызовов прекратился.

Реализация throttle

Идея: запоминаем время последнего вызова и пропускаем новые, пока не прошёл интервал. Здесь без таймеров — через сравнение времени.

function throttle(fn, interval) {
  let last = 0;               // время последнего вызова
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn(...args);
    }
  };
}

let count = 0;
const onScroll = throttle(() => { count++; }, 1000);

onScroll(); // первый проходит
onScroll(); // сразу следом — заблокирован
onScroll();
console.log("сработало раз:", count);

Вывод:

сработало раз: 1

Первый вызов прошёл, а следующие в пределах интервала отброшены — обработчик «троттлится» до одного раза в секунду.

Как отвечать на собеседовании

  • debounce — «подожди, пока перестанут печатать, и сделай один запрос».
  • throttle — «делай не чаще, чем раз в N миллисекунд».
  • Оба основаны на замыкании, которое хранит состояние между вызовами.

Итог

  • Оба приёма ограничивают частоту вызовов тяжёлых обработчиков.
  • debounce ждёт паузу и делает один вызов; throttle ограничивает частоту.
  • Реализуются через замыкание, хранящее таймер или время последнего вызова.
Проверьте себя
1. Чем debounce отличается от throttle?
AОни одинаковы
Bdebounce ждёт паузу и делает один вызов; throttle ограничивает частоту вызовов
Cthrottle вообще не вызывает функцию
Ddebounce работает только со скроллом
2. Что хранит замыкание в реализации debounce?
AМассив всех аргументов
BИдентификатор таймера между вызовами
CГлобальную переменную
DНичего
3. Где уместнее применить debounce?
AОбработка каждого пикселя скролла
BПоиск по мере ввода — запрос только когда пользователь перестал печатать
CАнимация кадров
DОднократный клик по кнопке
Поддержать проект