Debounce и throttle

В разработке на JavaScript часто возникает необходимость контролировать частоту выполнения функций, особенно при работе с событиями браузера, такими как scroll, resize, input и mousemove. Для этих целей применяются техники debounce и throttle, каждая из которых решает разные задачи и имеет свои особенности.


Debounce

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

Применение:

  • Отправка запросов на сервер после ввода текста в поле поиска.
  • Обработка изменения размеров окна (resize) для перерасчёта макета.
  • События прокрутки с постепенной реакцией.

Пример реализации:

function debounce(func, delay) {
    let timeoutId;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
}

// Использование:
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((event) => {
    console.log('Поиск:', event.target.value);
}, 300));

Особенности:

  • Последний вызов функции гарантированно выполняется через заданный интервал.
  • Часто используется для оптимизации операций с высокой частотой срабатывания событий.
  • Может иметь опцию immediate, позволяющую вызывать функцию в начале, а не в конце периода задержки.
function debounce(func, wait, immediate) {
    let timeout;
    return function (...args) {
        const context = this;
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            timeout = null;
            if (!immediate) func.apply(context, args);
        }, wait);
        if (callNow) func.apply(context, args);
    };
}

Throttle

Throttle ограничивает частоту вызова функции, гарантируя, что она будет выполнена не чаще одного раза в заданный промежуток времени. В отличие от debounce, функция вызывается регулярно, даже если события продолжают срабатывать.

Применение:

  • Обновление позиции элементов при прокрутке (scroll).
  • Анимации, зависящие от движения мыши.
  • Ограничение частоты отправки данных на сервер при постоянных событиях.

Пример реализации:

function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function (...args) {
        const context = this;
        if (!lastRan) {
            func.apply(context, args);
            lastRan = Date.now();
        } else {
            clearTimeout(lastFunc);
            lastFunc = setTimeout(() => {
                if ((Date.now() - lastRan) >= limit) {
                    func.apply(context, args);
                    lastRan = Date.now();
                }
            }, limit - (Date.now() - lastRan));
        }
    };
}

// Использование:
window.addEventListener('scroll', throttle(() => {
    console.log('Скролл с ограничением частоты');
}, 200));

Особенности:

  • Функция гарантированно вызывается через регулярные интервалы.
  • Throttle снижает нагрузку на систему при непрерывных событиях, сохраняя актуальность данных.
  • Подходит для долгоживущих потоков событий, когда важна периодическая реакция.

Отличия Debounce и Throttle

Параметр Debounce Throttle
Когда вызывается После окончания серии событий На регулярных интервалах
Цель Игнорировать промежуточные события Ограничить частоту вызова
Применение Поиск, resize, ввод текста Скролл, mousemove, анимации
Последний вызов Всегда происходит после задержки Может пропустить последний вызов

Выбор между debounce и throttle зависит от задачи: если важен финальный результат после серии событий — debounce, если важна периодическая реакция — throttle.


Практическая интеграция с библиотекой Navigo

Navigo — это лёгкий роутер для SPA, где debounce и throttle могут использоваться для оптимизации:

  • Debounce: при динамическом поиске по URL-параметрам перед вызовом маршрута.
  • Throttle: при обновлении маршрутов, привязанных к событиям прокрутки или анимации интерфейса.

Пример: динамический поиск с debounce в Navigo:

const router = new Navigo('/');

function loadSearchResults(query) {
    console.log('Загружаем результаты для:', query);
    // fetch(`/search?q=${query}`) ...
}

const debouncedSearch = debounce(loadSearchResults, 300);

router.on('/search/:query', (params) => {
    debouncedSearch(params.query);
}).resolve();

Пример: throttle для отслеживания скролла с маршрутизацией:

window.addEventListener('scroll', throttle(() => {
    const position = window.scrollY;
    if (position > 500) {
        router.navigate('/long-scroll');
    }
}, 200));

Дополнительные рекомендации

  • Всегда очищать таймеры при уничтожении компонентов или переходе между маршрутами, чтобы избежать утечек памяти.
  • Для сложных сценариев, где важна комбинация immediate и периодичности, можно создавать гибридные функции, объединяющие debounce и throttle.
  • Использовать библиотеки типа Lodash (_.debounce, _.throttle) для готовых и проверенных реализаций с расширенными опциями.

Эти техники позволяют значительно повысить производительность SPA и улучшить пользовательский опыт при высокочастотных событиях.