Debounce и throttle

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


Debounce

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

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

Пояснение работы:

  1. Переменная timeoutId хранит идентификатор текущего таймера.
  2. При каждом вызове функции таймер сбрасывается через clearTimeout.
  3. Новая функция откладывается на указанный delay.
  4. Если новые вызовы продолжаются, предыдущие таймеры отменяются.
  5. Функция выполняется только после того, как события перестают происходить.

Применение:

  • Автодополнение при вводе в поле (input).
  • Валидация формы после прекращения ввода.
  • Отправка аналитических событий, когда пользователь перестал взаимодействовать с элементом.
const searchInput = document.querySelector('#search');
const fetchResults = debounce((query) => {
  console.log(`Ищем: ${query}`);
}, 300);

searchInput.addEventListener('input', (e) => fetchResults(e.target.value));

Throttle

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

function throttle(func, limit) {
  let lastCall = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      func.apply(this, args);
    }
  };
}

Пояснение работы:

  1. Переменная lastCall хранит время последнего выполнения функции.
  2. Каждый вызов проверяет, прошло ли limit миллисекунд с момента последнего выполнения.
  3. Если интервал прошёл — функция выполняется, иначе — игнорируется.
  4. Функция будет выполняться регулярно, с ограниченной частотой, даже при постоянных событиях.

Применение:

  • Отслеживание прокрутки страницы (scroll) для показа анимаций.
  • Ограничение частоты обновления интерфейса при ресайзе окна (resize).
  • Контроль отправки событий на сервер для предотвращения перегрузки.
window.addEventListener('scroll', throttle(() => {
  console.log('Прокрутка с ограничением частоты');
}, 200));

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

Характеристика Debounce Throttle
Поведение Выполняется один раз после затишья Выполняется регулярно с фиксированным интервалом
Применение Ввод текста, автозавершение, валидация Скролл, ресайз, анимации
Интенсивность вызова Низкая, только после окончания событий Средняя, ограниченный поток вызовов
Контроль нагрузки Хороший для нерегулярных действий Хороший для постоянных событий

Debounce и Throttle в связке с Tippy.js

Tippy.js — библиотека для создания тултипов в веб-интерфейсах. При динамическом взаимодействии (например, hover по множеству элементов или обновление содержимого тултипов) важно оптимизировать производительность с помощью debounce и throttle.

Пример с debounce для динамического контента тултипа:

import tippy from 'tippy.js';

const input = document.querySelector('#username');

tippy(input, {
  content: 'Введите имя пользователя',
  onShow(instance) {
    updateTooltip(instance);
  }
});

const updateTooltip = debounce((instance) => {
  const value = input.value;
  instance.setContent(value ? `Вы ввели: ${value}` : 'Введите имя пользователя');
}, 300);

Пример с throttle для отслеживания движения мыши:

document.addEventListener('mousemove', throttle((e) => {
  const tooltipElements = document.querySelectorAll('.tooltip');
  tooltipElements.forEach(el => {
    const tip = el._tippy;
    if (tip) {
      tip.setContent(`Координаты: ${e.clientX}, ${e.clientY}`);
    }
  });
}, 100));

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


Настройка интервалов и контекста

  • Контекст (this) и аргументы функции сохраняются через apply.
  • Интервалы (delay для debounce, limit для throttle) подбираются эмпирически, исходя из частоты события и требуемой отзывчивости интерфейса.
  • Для Tippy.js рекомендуется использовать debounce при обновлении содержимого и throttle при обновлении позиций тултипов при движении или скролле.

Комбинирование техник

В сложных интерфейсах можно комбинировать debounce и throttle:

  • Throttle контролирует частоту движения тултипа.
  • Debounce обновляет содержимое после прекращения активного ввода или наведения.
const input = document.querySelector('#search');
tippy(input, {
  content: 'Поиск...',
  onShow(instance) {
    const update = debounce(() => {
      instance.setContent(`Результаты для: ${input.value}`);
    }, 300);
    update();
  }
});

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