Напишите функцию 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 и приводят неподходящие примеры применения