← Назад к списку
ПрограммированиеFrontendMiddle

Напишите функцию debounce. Чем она отличается от throttle и где что применять?

Короткий ответ

  • Debounce откладывает вызов до паузы в событиях
  • Каждое новое событие сбрасывает таймер
  • Throttle гарантирует не чаще одного вызова в интервал
  • Реализация — замыкание, хранящее id таймера
  • Важно пробросить this и аргументы
  • Debounce: поиск по вводу, ресайз; throttle: скролл, курсор
  • Полезно добавить метод cancel для размонтирования

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

Как сказать вслух

пример ответа

Debounce откладывает выполнение функции, пока события не прекратятся: каждый новый вызов сбрасывает таймер, и функция сработает один раз после паузы. Это идеально для поиска по мере ввода — запрос уйдёт, когда пользователь перестал печатать. Throttle другой: он пропускает не больше одного вызова за интервал, события при этом продолжают обрабатываться равномерно — так делают обработку скролла. Реализуется debounce замыканием: храню id таймера, при каждом вызове очищаю и ставлю заново.

Подробный ответ

Основной ответ

Реализация: внешняя функция принимает fn и delay, в замыкании хранит timerId и возвращает обёртку. Обёртка при каждом вызове делает clearTimeout(timerId) и ставит setTimeout, который вызовет fn через delay после последнего вызова. Критичные детали, которые проверяет интервьюер: обёртка должна быть обычной функцией (не стрелкой), чтобы передать this через fn.apply(this, args); аргументы последнего вызова сохраняются rest-параметром; хорошим тоном будет метод cancel для отмены отложенного вызова — без него возможен вызов колбэка после размонтирования компонента. Расширенные версии поддерживают опцию leading (сработать сразу, затем молчать) и возврат результата. Throttle реализуется либо флагом с таймером, либо сравнением временных меток Date.now(); продвинутые версии комбинируют leading и trailing вызовы.

Ключевые моменты

  • Сброс таймера. Суть debounce — clearTimeout перед каждым новым setTimeout; выполнение происходит только после паузы длиной delay.
  • this и аргументы. fn.apply(this, args) в обычной функции сохраняет контекст и параметры последнего вызова — частая точка провала на интервью.
  • Отмена. Метод cancel обязателен в реальном коде: отложенный вызов не должен стрелять после ухода со страницы.
  • Выбор инструмента. Нужен финальный результат серии — debounce; нужна регулярная обработка потока — throttle.

Практический контекст

Одна из самых частых задач на лайв-кодинге по фронтенду. В работе встречается в поиске с подсказками, автосохранении, валидации на вводе, обработке resize и scroll. В React самодельный debounce оборачивают в useMemo/useRef, чтобы таймер переживал ререндеры, — об этом часто спрашивают следующим шагом. Упоминание готовых реализаций (lodash.debounce) уместно, но писать нужно уметь руками.

Пример кода

function debounce(fn, delay) {
  let timerId = null;
  function debounced(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), delay);
  }
  debounced.cancel = () => clearTimeout(timerId);
  return debounced;
}

const onInput = debounce(q => searchApi(q), 300);
input.addEventListener('input', e => onInput(e.target.value));

Частые ошибки

  • Забывают clearTimeout, получая вызов на каждое событие с задержкой
  • Теряют this и аргументы, используя стрелку и вызывая fn() без apply
  • Путают debounce с throttle и приводят неподходящие примеры применения

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru