Throttle и debounce для событий

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


Debounce

Debounce позволяет ограничить вызов функции до момента, когда события перестанут происходить в течение определённого времени. То есть функция будет вызвана только один раз после «задержки», независимо от того, сколько раз срабатывало событие за этот промежуток.

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

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), wait);
    };
}

Применение:

window.addEventListener('resize', debounce(() => {
    console.log('Окно изменено');
}, 300));

В этом примере функция console.log будет вызвана только один раз через 300 мс после последнего события resize.

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

  • Полезен для событий, которые происходят очень часто, например, ввод текста в поле поиска.
  • Можно добавить опцию leading, чтобы функция вызывалась сразу при первом событии, а не только после паузы.
  • Эффективен для экономии ресурсов и предотвращения «подвисаний» интерфейса.

Throttle

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

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

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(function() {
                if ((Date.now() - lastRan) >= limit) {
                    func.apply(context, args);
                    lastRan = Date.now();
                }
            }, limit - (Date.now() - lastRan));
        }
    };
}

Применение:

window.addEventListener('scroll', throttle(() => {
    console.log('Прокрутка страницы');
}, 200));

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

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

  • Позволяет контролировать поток событий без потери важной информации.
  • Часто используется для событий scroll, mousemove и анимаций.
  • Можно настроить выполнение сразу при старте события или в конце интервала.

Сравнение Debounce и Throttle

Параметр Debounce Throttle
Цель Выполнение функции после прекращения события Выполнение функции через фиксированные интервалы времени
Пример событий input, keyup, поиск scroll, mousemove, resize
Частота вызовов Один раз после паузы Регулярно, через заданный интервал
Производительность Минимизирует нагрузку при частых событиях Баланс между отзывчивостью и нагрузкой
Опция «leading» Да Да, зависит от реализации

Использование с Headroom.js

Библиотека Headroom.js активно использует scroll события для управления скрытием и отображением шапки страницы. Без throttle или debounce обработчик будет срабатывать слишком часто, создавая нагрузку и дерганую анимацию.

Пример интеграции с throttle:

const header = document.querySelector('.header');
const headroom = new Headroom(header, {
    tolerance: {
        up: 5,
        down: 0
    },
    offset: 100,
    onPin: () => console.log('header pinned'),
    onUnpin: () => console.log('header unpinned')
});

// Оборачиваем scroll обработчик в throttle для оптимизации
window.addEventListener('scroll', throttle(() => {
    headroom.update();
}, 100));

Такой подход гарантирует, что Headroom.js будет обновляться не чаще одного раза в 100 мс, что плавно управляет видимостью шапки и снижает нагрузку на браузер.


Практические рекомендации

  1. Для событий с высокой частотой (scroll, mousemove) предпочтителен throttle.

  2. Для событий, где важно дождаться завершения действия пользователя (поиск, ввод текста) — debounce.

  3. Оптимальные значения интервалов:

    • Scroll: 50–200 мс
    • Resize: 100–300 мс
    • Input: 200–400 мс
  4. Всегда учитывать контекст вызова (this) при обёртке функций.

  5. Для Headroom.js throttle обеспечивает плавность анимаций и отзывчивость интерфейса без излишних перерисовок.


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