Дебаунс и троттлинг в JavaScript: понятное руководство с примерами

Дебаунс и троттлинг в JavaScript: понятное руководство с примерами

Дебаунс и троттлинг в JavaScript: понятное руководство с примерами

Когда пользователь печатает в поле ввода, скроллит страницу или меняет размер окна, обработчики событий могут вызываться десятки раз в секунду. Без ограничений это приводит к лагам, лишним запросам и «тяжелому» интерфейсу. На помощь приходят два приема: дебаунс (debounce) и троттлинг (throttle).

Что такое дебаунс и троттлинг

  • Дебаунс — запускает функцию только после того, как поток событий закончился и прошло заданное время ожидания. Полезно для автопоиска, валидации, автосохранения.
  • Троттлинг — ограничивает частоту вызовов, гарантируя, что функция не выполнится чаще заданного интервала. Идеален для scroll, resize, drag, mousemove.
  • Дебаунс: реализация и применение

    function debounce(fn, wait = 300, { leading = false, trailing = true } = {}) {
      let timer = null;
      let lastArgs, lastThis, result;
    
      const invoke = () => {
        timer = null;
        if (trailing && lastArgs) {
          result = fn.apply(lastThis, lastArgs);
          lastArgs = lastThis = null;
        }
      };
    
      function debounced(...args) {
        lastArgs = args;
        lastThis = this;
    
        if (timer) clearTimeout(timer);
    
        if (leading && !timer) {
          result = fn.apply(lastThis, lastArgs);
          lastArgs = lastThis = null;
        }
    
        timer = setTimeout(invoke, wait);
        return result;
      }
    
      debounced.cancel = () => {
        if (timer) clearTimeout(timer);
        timer = null;
        lastArgs = lastThis = null;
      };
    
      debounced.flush = () => {
        if (timer) {
          clearTimeout(timer);
          invoke();
        }
        return result;
      };
    
      return debounced;
    }
    

    Пример: автопоиск по мере ввода

    const input = document.querySelector('#search');
    
    const fetchResults = async (q) => {
      if (!q) return [];
      const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
      return res.json();
    };
    
    const onSearch = debounce(async (q) => {
      const data = await fetchResults(q);
      console.log('Результаты:', data);
    }, 400);
    
    input.addEventListener('input', (e) => onSearch(e.target.value));
    

    Советы по дебаунсу:

  • Для «живого поиска» чаще всего подходят параметры: wait = 300–500 мс, leading = false, trailing = true.
  • Для мгновенного отклика при первом вводе можно включить leading: true и оставить trailing: true для финального запроса.
  • При смене страницы/компонента вызывайте debounced.cancel() для очистки таймера.
  • Троттлинг: реализация и применение

    function throttle(fn, wait = 200, { leading = true, trailing = true } = {}) {
      let lastCall = 0;
      let timer = null;
      let lastArgs, lastThis, result;
    
      const invoke = (time) => {
        lastCall = time;
        result = fn.apply(lastThis, lastArgs);
        lastArgs = lastThis = null;
      };
    
      function throttled(...args) {
        const now = Date.now();
        if (!lastCall && leading === false) lastCall = now;
    
        const remaining = wait - (now - lastCall);
        lastArgs = args;
        lastThis = this;
    
        if (remaining <= 0 || remaining > wait) {
          if (timer) { clearTimeout(timer); timer = null; }
          invoke(now);
        } else if (trailing !== false && !timer) {
          timer = setTimeout(() => {
            timer = null;
            invoke(Date.now());
          }, remaining);
        }
        return result;
      }
    
      throttled.cancel = () => {
        if (timer) clearTimeout(timer);
        timer = null;
        lastCall = 0;
        lastArgs = lastThis = null;
      };
    
      return throttled;
    }
    

    Пример: оптимизация скролла

    const progress = document.querySelector('#progress');
    
    function updateProgress() {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      const value = (window.scrollY / max) * 100;
      progress.style.width = `${Math.min(100, Math.max(0, value))}%`;
    }
    
    const onScroll = throttle(updateProgress, 100, { leading: true, trailing: true });
    
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('load', updateProgress);
    

    Советы по троттлингу:

  • Для scroll/resize используйте { passive: true } в addEventListener, чтобы ускорить прокрутку.
  • Интервал 50–200 мс обычно дает хороший баланс между плавностью и нагрузкой.
  • Если важно мгновенно реагировать, включайте leading: true.
  • Debounce vs Throttle: когда что выбрать

  • Debounce — когда важен только итоговый, «финальный» вызов после серии событий: поиск, автосохранение, валидация формы.
  • Throttle — когда нужно ограничить частоту: визуальные эффекты при скролле, пауза между drag-событиями, обработка resize.
  • Типичные ошибки и как их избежать

  • Потеря контекста this. Если оборачиваете метод объекта, не используйте стрелочную функцию при объявлении метода, либо заранее bindьте: obj.method = debounce(obj.method.bind(obj), 300).
  • Не снимаете обработчики. При уничтожении виджетов/компонентов вызывайте cancel() и removeEventListener.
  • Слишком большой интервал. Слишком “жесткий” троттлинг делает интерфейс «дерганым». Подберите значение на практике.
  • Игнорирование rAF. Для чисто визуальных обновлений попробуйте requestAnimationFrame.
  • Альтернатива с requestAnimationFrame для скролла

    let ticking = false;
    function onScrollRaf() {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          updateProgress();
          ticking = false;
        });
        ticking = true;
      }
    }
    window.addEventListener('scroll', onScrollRaf, { passive: true });
    

    Готовые решения из экосистемы

    Если не хочется писать свои утилиты, используйте battle-tested реализации из lodash:

    import debounce from 'lodash/debounce';
    import throttle from 'lodash/throttle';
    
    const onInput = debounce(handleChange, 300, { leading: false, trailing: true });
    const onMove  = throttle(handleMove, 100, { leading: true, trailing: true });
    

    Lodash корректно обрабатывает тонкости, имеет методы cancel и flush, а также стабильные API, что уменьшает риск багов в продакшене.

    Итоги

    Дебаунс и троттлинг — простые, но крайне эффективные инструменты повышения производительности фронтенда. Освойте их — и ваши интерфейсы станут быстрее и стабильнее. Хотите системно прокачать JavaScript с практикой и проектами? Загляните сюда: Пройти практический курс «JavaScript с Нуля до Гуру 2.0» и посмотреть программу.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий