Когда использовать throttle?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Throttle: когда и зачем использовать
Throttle (дросселирование) — это техника оптимизации производительности, которая ограничивает частоту вызова функции. Функция вызывается максимум один раз за определённый промежуток времени, независимо от того, сколько раз произошло событие.
Проблема, которую решает throttle
Представьте, что пользователь скролит страницу:
// Без throttle: event срабатывает может быть 100+ раз в секунду
window.addEventListener('scroll', () => {
updateScrollPosition(); // Вызывается 100+ раз!
saveToDatabase(); // Отправляет 100+ запросов
updateAnalytics(); // 100+ запросов на аналитику
});
Это приводит к:
- Перегрузке CPU
- Лагам и зависанию интерфейса
- Огромному количеству ненужных запросов к серверу
- Истощению батареи на мобильных устройствах
Throttle решает это, гарантируя, что функция вызывается максимум один раз в N миллисекунд.
Throttle vs Debounce
Это важное различие, которое спрашивают на интервью:
| Параметр | Throttle | Debounce |
|---|---|---|
| Вызов | Регулярно (каждые N мс) | Только после паузы |
| Срабатывание | Во время события | После завершения события |
| Использование | Scroll, resize, mousemove | Поиск, resize окна, фильтр |
| Гарантия | Функция вызывается N раз за T времени | Функция вызывается 1 раз после T времени |
Пример:
- Throttle при скролле: каждые 100мс отправляем аналитику, пока пользователь скролит
- Debounce при поиске: отправляем поиск только после того, как пользователь 300мс не печатал
Реализация throttle
Базовая версия
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Использование
const handleScroll = throttle(() => {
console.log('Scroll event');
}, 1000); // Максимум один раз в секунду
window.addEventListener('scroll', handleScroll);
Продвинутая версия (с leading и trailing)
function throttle(func, limit, options = {}) {
let inThrottle, lastRan;
const { leading = true, trailing = true } = options;
return function(...args) {
const now = Date.now();
// Вызываем функцию в начале периода (leading)
if (!lastRan && leading) {
func.apply(this, args);
lastRan = now;
} else {
// Очищаем предыдущий timeout
clearTimeout(inThrottle);
// Вызываем функцию в конце периода (trailing)
if (trailing) {
inThrottle = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (now - lastRan));
}
}
};
}
Когда использовать throttle
1. Scroll event (самый частый случай)
const handleScroll = throttle(() => {
const scrollTop = window.scrollY;
console.log('Текущая позиция:', scrollTop);
// Отправляем аналитику
trackScrollEvent(scrollTop);
// Загружаем больше контента
if (isNearBottom()) {
loadMoreContent();
}
}, 300); // Каждые 300мс
window.addEventListener('scroll', handleScroll);
2. Resize event
const handleResize = throttle(() => {
const width = window.innerWidth;
const height = window.innerHeight;
// Пересчитываем layout
recalculateLayout(width, height);
}, 500);
window.addEventListener('resize', handleResize);
3. Mouse move (отслеживание курсора)
const handleMouseMove = throttle((event) => {
const { clientX, clientY } = event;
// Отправляем координаты на сервер (для многопользовательских приложений)
updateCursorPosition(clientX, clientY);
}, 100); // Обновляем позицию курсора каждые 100мс
document.addEventListener('mousemove', handleMouseMove);
4. Отправка API запросов (например, при вводе параметров фильтра)
const handleFilterChange = throttle((filterValue) => {
// Отправляем фильтр максимум раз в 500мс
fetchFilteredResults(filterValue);
}, 500);
input.addEventListener('input', (e) => {
handleFilterChange(e.target.value);
});
Обратите внимание: этот случай близок к debounce, но throttle гарантирует, что мы отправляем запрос регулярно, а не только в конце.
5. Обновление фиксированного навигационного элемента при скролле
const updateNavBar = throttle(() => {
const scrollY = window.scrollY;
const navBar = document.querySelector('.navbar');
if (scrollY > 100) {
navBar.classList.add('scrolled');
} else {
navBar.classList.remove('scrolled');
}
}, 100);
window.addEventListener('scroll', updateNavBar);
Реальный пример: infinite scroll с throttle
class InfiniteScroll {
constructor() {
this.isLoading = false;
this.page = 1;
// Throttle функцию проверки
this.checkScroll = throttle(() => {
this.loadMoreIfNeeded();
}, 300);
window.addEventListener('scroll', this.checkScroll);
}
loadMoreIfNeeded() {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight;
const windowHeight = window.innerHeight;
// Если пользователь прокрутил до конца
if (scrollTop + windowHeight > docHeight - 500 && !this.isLoading) {
this.loadMore();
}
}
async loadMore() {
this.isLoading = true;
this.page++;
try {
const response = await fetch(`/api/items?page=${this.page}`);
const data = await response.json();
this.renderItems(data);
} finally {
this.isLoading = false;
}
}
}
Практические рекомендации
Выбор промежутка времени
- 100мс — для очень частых событий (mousemove, wheel)
- 300мс — для scroll, resize
- 500мс — для API запросов, фильтров
- 1000мс — для аналитики, логирования
Когда NOT использовать throttle
- Клики по кнопкам — используй обычный click handler с флагом isLoading
- Форм-события — используй debounce вместо throttle
- Единовременные события — нет смысла в throttle
Альтернативы
requestAnimationFrame (рекомендуется для визуальных обновлений)
let ticking = false;
function updateScroll() {
// Обновляем визуальное состояние
renderUI();
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(updateScroll);
ticking = true;
}
});
Это лучше, чем setTimeout, потому что синхронизирует с частотой обновления экрана (60 FPS).
Использование lodash
В реальных проектах часто используют готовые решения:
import { throttle } from 'lodash';
const handleScroll = throttle(() => {
updateUI();
}, 300);
window.addEventListener('scroll', handleScroll);
Типичные ошибки
// Ошибка 1: Создание новой функции throttle каждый раз
window.addEventListener('scroll', () => {
throttle(() => updateUI(), 300)(); // Неправильно!
});
// Правильно:
const handleScroll = throttle(() => updateUI(), 300);
window.addEventListener('scroll', handleScroll);
// Ошибка 2: Забыли bind
const obj = {
count: 0,
increment: throttle(function() {
this.count++; // this может быть undefined!
}, 100)
};
Ключевые выводы
- Throttle = максимум одного вызова в N мс
- Используй для высокочастотных событий: scroll, resize, mousemove
- Debounce для событий, где важен финальный результат: поиск, валидация
- requestAnimationFrame лучше для визуальных обновлений
- Не забывай о производительности: неправильная throttle ухудшит UX