← Назад к вопросам
Реализовать функцию throttle
1.7 Middle🔥 241 комментариев
#JavaScript Core
Условие
Напишите функцию throttle(fn, interval), которая ограничивает частоту вызова переданной функции fn - не чаще, чем раз в interval миллисекунд.
Требования
-
Функция должна принимать два аргумента:
- fn - функция, вызов которой нужно ограничить
- interval - минимальный интервал между вызовами в миллисекундах
-
Возвращаемая функция должна:
- Вызываться сразу при первом обращении
- Игнорировать вызовы в течение интервала
- Корректно передавать аргументы в оригинальную функцию
Пример использования
var throttledScroll = throttle(handleScroll, 1000);
function handleScroll() {
console.log(Date.now());
}
window.addEventListener("scroll", throttledScroll);
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Реализация функции throttle
Различие между throttle и debounce
В отличие от debounce, который откладывает вызов до конца периода спокойствия, throttle выполняет функцию максимум один раз за заданный интервал времени. Это критично при обработке часто возникающих событий типа scroll или mousemove, где нужна регулярная обработка без перегрузки.
Базовая реализация
function throttle(fn, interval) {
let lastCallTime = 0;
return function throttled(...args) {
const now = Date.now();
if (now - lastCallTime >= interval) {
lastCallTime = now;
fn.apply(this, args);
}
};
}
Версия с трейлингим вызовом
function throttle(fn, interval) {
let lastCallTime = 0;
let timeoutId = null;
const throttled = function(...args) {
const now = Date.now();
if (now - lastCallTime >= interval) {
// Очищаем таймер если был
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastCallTime = now;
fn.apply(this, args);
} else if (timeoutId === null) {
// Планируем вызов в конце интервала
const remainingTime = interval - (now - lastCallTime);
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
fn.apply(this, args);
timeoutId = null;
}, remainingTime);
}
};
throttled.cancel = () => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return throttled;
}
TypeScript версия
type ThrottledFn<T extends (...args: any[]) => any> = (
...args: Parameters<T>
) => void & { cancel: () => void };
function throttle<T extends (...args: any[]) => any>(
fn: T,
interval: number
): ThrottledFn<T> {
let lastCallTime = 0;
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const throttled = function(this: any, ...args: Parameters<T>) {
const now = Date.now();
const timeSinceLastCall = now - lastCallTime;
if (timeSinceLastCall >= interval) {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastCallTime = now;
fn.apply(this, args);
} else if (timeoutId === null) {
const remainingTime = interval - timeSinceLastCall;
timeoutId = setTimeout(() => {
lastCallTime = Date.now();
fn.apply(this, args);
timeoutId = null;
}, remainingTime);
}
};
throttled.cancel = () => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return throttled as ThrottledFn<T>;
}
Варианты с опциями
function throttle(fn, interval, options = {}) {
const { leading = true, trailing = true } = options;
let lastCallTime = leading ? 0 : Date.now();
let timeoutId = null;
return function throttled(...args) {
const now = Date.now();
if (!leading && lastCallTime === 0) {
lastCallTime = now;
}
const timeSinceLastCall = now - lastCallTime;
if (timeSinceLastCall >= interval) {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastCallTime = now;
fn.apply(this, args);
} else if (trailing && timeoutId === null) {
const remainingTime = interval - timeSinceLastCall;
timeoutId = setTimeout(() => {
if (trailing) {
fn.apply(this, args);
}
timeoutId = null;
}, remainingTime);
}
};
}
// leading: true - вызывает сразу при первом обращении
// trailing: true - вызывает в конце интервала если было несколько вызовов
Примеры использования
// Обработка скролла
function handleScroll() {
console.log('Scroll event at', Date.now());
}
const throttledScroll = throttle(handleScroll, 1000);
window.addEventListener('scroll', throttledScroll);
// Обработка изменения размера окна
function handleResize() {
console.log('Window resized');
}
const throttledResize = throttle(handleResize, 500);
window.addEventListener('resize', throttledResize);
// Движение мыши
function handleMouseMove(e) {
console.log(`Mouse: ${e.clientX}, ${e.clientY}`);
}
const throttledMove = throttle(handleMouseMove, 100);
document.addEventListener('mousemove', throttledMove);
// Авто-сохранение
const saveData = throttle(() => {
console.log('Saving...');
fetch('/api/save', { method: 'POST' });
}, 5000);
input.addEventListener('input', saveData);
// Отмена
const fn = throttle(() => console.log('Executed'), 1000);
fn();
fn.cancel(); // Не выполнится отложенный вызов
Визуализация throttle vs debounce
Вызовы: |----|----|----|----|----|----|----|
Throttle |####| |####| |####| |####|
(каждую секунду)
Debounce | |####|
(после паузы)
Территория: 4 вызова, интервал 1 сек
Пошаговый разбор
const fn = throttle(() => console.log(Date.now()), 1000);
fn(); // t=0ms → вызывает сразу
fn(); // t=100ms → игнорирует
fn(); // t=500ms → игнорирует
fn(); // t=1500ms → вызывает (прошло 1500мс)
fn(); // t=1600ms → игнорирует
fn(); // t=2000ms → вызывает (прошло 500мс с последнего)
Сравнение подходов
| Подход | Leading | Trailing | Плюсы | Минусы |
|---|---|---|---|---|
| Простой | true | false | Легко реализовать | Теряет последние данные |
| С trailing | true | true | Полнота данных | Сложнее |
| С опциями | любой | любой | Гибкость | Много кода |
Реальные применения
- Scroll события: обновление UI при прокрутке
- Resize события: пересчёт layout при изменении размера
- Search input: дебаунсинг API запросов (часто комбинируют)
- Mouse move: отслеживание курсора без перегрузки
- Analytics: отправка событий регулярно
Ключевые моменты
- lastCallTime: отслеживаем время последнего вызова
- Date.now(): высокоточное время вызова
- timeSinceLastCall: проверяем прошёл ли интервал
- Trailing option: вызов в конце интервала если надо
- Cancel метод: возможность отменить отложенный вызов