Вы когда-нибудь замечали, как сайт начинает тормозить, когда вы быстро печатаете в поиске или прокручиваете страницу? Это не баг - это просто JavaScript, который перегружен событиями. Каждое нажатие клавиши, каждый сдвиг мыши, каждый скролл - это вызов функции. Если их обрабатывать все подряд, браузер сходит с ума. Вот где приходят на помощь троттлинг и дебаунс. Эти два инструмента не просто улучшают производительность - они делают интерфейсы живыми, быстрыми и предсказуемыми.
Что такое дебаунс и зачем он нужен
Дебаунс работает по принципу: «Подожди, пока человек перестанет делать что-то, и только потом сделай работу». Представьте, что вы пишете поиск. Пользователь вводит «квартира в Казани». Он не ждёт, пока он наберёт всё - он печатает по буквам. Без дебаунса вы отправите 11 запросов к серверу: на «к», на «кв», на «квa», и так далее. Это лишняя нагрузка, лишние деньги на хостинг, лишнее время ожидания для пользователя.
Дебаунс берёт все эти вызовы и объединяет их в один. Он ставит таймер на 400 миллисекунд. Каждый раз, когда пользователь нажимает клавишу, таймер сбрасывается. Только когда он перестал печатать - и прошло 400 мс - тогда и отправляется запрос. Всё, что происходило до этого, игнорируется. Результат? Один запрос вместо десятка.
Это идеально для:
- Поисковых полей
- Автосохранения форм
- Валидации данных
- Обновления настроек в реальном времени
Реализация простая. Создаёте переменную для хранения таймера. При каждом вызове функции - очищаете старый таймер, ставите новый. Если таймер не сработал - функция не выполняется. Вот как это выглядит в коде:
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const searchHandler = debounce(fetchResults, 400);
input.addEventListener('input', searchHandler);
Здесь fetchResults - ваша настоящая функция, которая делает запрос. А searchHandler - обёртка, которая ждёт, пока пользователь перестанет печатать. И да, вы можете настроить, чтобы функция сработала сразу в начале (leading), а не только в конце (trailing). Но по умолчанию - только в конце.
Что такое троттлинг и когда его использовать
Троттлинг - это противоположность дебаунсу. Он не ждёт, пока событие закончится. Он говорит: «Слишком много вызовов? Хорошо. Я буду выполнять твою функцию только раз в 100 мс. Остальное - игнорирую».
Представьте, что вы отслеживаете скролл. Пользователь прокручивает страницу - браузер генерирует сотни событий в секунду. Если вы обновляете индикатор прогресса при каждом событии - вы заставляете браузер перерисовывать элемент 100 раз в секунду. Это жестоко. Троттлинг решает это: он даёт вам ровно 10 обновлений в секунду. Ровно. Регулярно. Без перегрузки.
Это идеально для:
- Скролла (progress bar, параллакс)
- Изменения размера окна (resize)
- Движения мыши (mousemove)
- Drag-and-drop с отслеживанием позиции
Как это работает? У вас есть переменная, которая хранит время последнего выполнения. При каждом вызове вы проверяете: прошло ли уже 100 мс? Если да - выполняете функцию и обновляете время. Если нет - просто ждёте. Можно также добавить «leading» (выполнить сразу при первом вызове) и «trailing» (выполнить, если событие закончилось, но не успело сработать).
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
const updateProgress = throttle(() => {
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
progressBar.style.width = scrollPercent + '%';
}, 100);
window.addEventListener('scroll', updateProgress, { passive: true });
Обратите внимание на { passive: true }. Это не просто опция - это важный трюк. Он говорит браузеру: «Этот обработчик не будет вызывать preventDefault()». Браузер может оптимизировать скролл, не дожидаясь JS. Без этого - скролл будет лагать даже с троттлингом.
Разница между дебаунсом и троттлингом - в сути
Многие путают эти два метода. Но разница простая:
| Параметр | Дебаунс | Троттлинг |
|---|---|---|
| Цель | Выполнить один раз после паузы | Выполнять с фиксированным интервалом |
| Когда срабатывает | После окончания серии событий | Первый раз сразу, потом через интервал |
| Пример | Поиск: запрос только после окончания ввода | Скролл: обновление прогресса каждые 100 мс |
| Если событие не прекращается | Функция никогда не сработает | Функция сработает через каждый интервал |
Дебаунс - это «ждём конца». Троттлинг - это «не перегружайся». Выбор между ними зависит от того, что вам важнее: точность результата или стабильность обновлений.
Почему стоит использовать Lodash
Вы можете написать свои версии дебаунса и троттлинга. Это полезно для обучения. Но в продакшене? Лучше использовать Lodash. Его функции _.debounce() и _.throttle() протестированы миллионами разработчиков. Они учитывают edge-кейсы: отмену, принудительный запуск, правильную работу с this, обработку аргументов, работу в строгом режиме. Вы не сможете написать лучше. И вы не захотите.
import { debounce, throttle } from 'lodash';
const search = debounce(fetchAPI, 400);
const scrollHandler = throttle(updateProgress, 100);
Lodash - это не «ещё одна библиотека». Это стандарт де-факто для таких задач. Если вы работаете в команде - используйте его. Это сэкономит вам часы отладки.
Ошибки, которые делают новички
Вот что часто идёт не так:
- Слишком маленький интервал. Дебаунс на 50 мс - это почти без него. Троттлинг на 20 мс - вы снова перегружаете браузер.
- Слишком большой интервал. Дебаунс на 2 секунды? Пользователь подумает, что система зависла. Троттлинг на 1 секунду? Прогресс-бар будет прыгать, как булавка.
- Игнорирование passive: true. Без этого скролл будет тормозить даже с троттлингом.
- Использование дебаунса там, где нужен троттлинг. Например, для отслеживания мыши. Вы будете ждать, пока пользователь перестанет двигать мышь - а он её не перестаёт. Результат - никаких обновлений.
- Забыть про отмену. Если пользователь закрыл окно, а таймер всё ещё висит - вы тратите память. Добавьте
cancel()илиclearTimeout()при уничтожении компонента.
Правило простое: начните с 300-400 мс для дебаунса и 100-150 мс для троттлинга. Тестируйте. Смотрите, как ведёт себя интерфейс. Не гонитесь за идеалом - гонитесь за плавностью.
Когда не нужно использовать дебаунс и троттлинг
Эти методы - не волшебная таблетка. Есть сценарии, где они не помогут, а только навредят:
- Кнопки. Нажали - выполнили. Нет смысла ждать.
- Формы с отправкой. Если пользователь нажал «Отправить» - не ждите. Выполняйте сразу.
- Анимации с requestAnimationFrame. Для анимаций есть свой механизм. Троттлинг тут не нужен.
- Реактивные системы. В React, Vue, Svelte часто используются хуки и реактивность. Дебаунс там не всегда нужен - он может нарушить логику.
Не используйте эти техники ради них самих. Используйте, когда они решают реальную проблему: слишком много вызовов, тормозит интерфейс, растёт нагрузка на сервер.
Чем отличается дебаунс от троттлинга?
Дебаунс ждёт, пока пользователь перестанет взаимодействовать, и выполняет функцию один раз в конце. Троттлинг выполняет функцию с фиксированным интервалом - например, каждые 100 мс, независимо от того, сколько раз событие произошло. Дебаунс подходит для поиска, троттлинг - для скролла.
Какой интервал выбрать для дебаунса?
Для поиска - 300-500 мс. Для автосохранения - 1000-2000 мс. Меньше - будет лишний трафик. Больше - пользователь подумает, что система не отвечает. Начните с 400 мс и тестируйте на реальных пользователях.
Можно ли использовать оба метода вместе?
Да, и это часто делают. Например, при скролле вы можете использовать троттлинг для обновления индикатора, а дебаунс - для сохранения позиции скролла, когда пользователь перестал прокручивать. Разные задачи - разные инструменты.
Почему троттлинг работает лучше при скролле, чем дебаунс?
Потому что при скролле пользователь не «перестаёт» - он постоянно двигает колесико. Дебаунс в этом случае никогда не сработает. Троттлинг же гарантирует, что обновление будет происходить регулярно - например, каждые 100 мс - даже если событий 500 за секунду.
Нужно ли писать свои реализации?
Только если вы учитесь. В реальных проектах используйте Lodash или аналоги. Они учитывают краевые случаи: отмену, контекст this, работу с аргументами, производительность. Ваша версия может работать, но не будет такой надёжной.
Если вы ещё не применяете троттлинг и дебаунс - начните с одного события. Возьмите поиск на сайте, оберните его в дебаунс с задержкой 400 мс. Запустите. Посмотрите, как уменьшилась нагрузка на сервер. Это не теория - это реальная экономия ресурсов. И плавный, быстрый интерфейс - это не фича. Это ожидание пользователя.