TL;DR: Наивная реализация throttle может потерять финальное состояние события, что приводит к багам. Используйте trailing throttle, чтобы гарантировать обработку последнего события после завершения взаимодействия.
Введение: контекст и актуальность
Throttling — это техника, которая позволяет ограничить частоту вызова функции, что особенно полезно при обработке событий, таких как resize или scroll. Однако многие разработчики используют базовую реализацию throttle, которая может привести к потере финального состояния события. Это происходит, когда пользователь завершает взаимодействие (например, прекращает скроллить), но последнее событие не обрабатывается из-за таймера.
В этой статье мы разберем, почему это происходит, и покажем, как trailing throttle решает эту проблему, гарантируя обработку последнего события.
Основная часть: проблема и решение
Наивная реализация throttle
Рассмотрим классическую реализацию throttle:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
Эта функция ограничивает вызовы func до одного раза в limit миллисекунд. Проблема в том, что если событие происходит сразу после завершения таймера, но до того, как inThrottle сбросится, последнее событие будет проигнорировано.
Демонстрация проблемы
Представим, что мы используем эту функцию для обработки события scroll:
window.addEventListener('scroll', throttle(() => {
console.log('Scroll event');
}, 100));
Если пользователь быстро прокручивает страницу, а затем останавливается, последний scroll может быть потерян, что приведет к тому, что состояние страницы не будет обновлено.
Решение: trailing throttle
Trailing throttle гарантирует, что последнее событие будет обработано, даже если оно происходит после завершения таймера. Вот как это можно реализовать:
function trailingThrottle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
Эта версия throttle сначала выполняет функцию, а затем устанавливает таймер для следующего вызова. Если событие происходит после завершения таймера, оно все равно будет обработано.
Практическое применение
Использование trailing throttle особенно полезно в следующих сценариях:
- Скроллинг: Гарантирует, что последний
scrollбудет обработан, что важно для корректного отображения контента. - Ресайз: Обеспечивает, что финальное состояние окна будет учтено после завершения изменения размера.
- Ввод пользователя: Позволяет обработать последний ввод пользователя, даже если он происходит сразу после завершения таймера.
Пример использования trailing throttle для обработки события resize:
window.addEventListener('resize', trailingThrottle(() => {
console.log('Window resized');
}, 100));
Заключение
Throttling — мощный инструмент для управления частотой событий, но его наивная реализация может привести к потере финального состояния. Использование trailing throttle решает эту проблему, гарантируя, что последнее событие будет обработано. Это делает trailing throttle предпочтительным выбором для большинства сценариев, где важно учитывать финальное состояние взаимодействия.
Используйте trailing throttle везде, где требуется контроль частоты событий, чтобы избежать багов и улучшить пользовательский опыт.
Источник: https://blog.gaborkoos.com/posts/2026-03-31-Your-Throttling-Is-Lying-to-You/