← Назад к вопросам
Реализуйте функцию throttle
2.0 Middle🔥 121 комментариев
#Node.js и JavaScript#Кэширование и производительность
Условие
Напишите функцию throttle(fn, limit), которая гарантирует, что функция fn вызывается не чаще, чем раз в limit миллисекунд.
function throttle(fn, limit) {
// Ваш код
}
// Пример использования:
const throttledScroll = throttle(() => console.log("scroll"), 1000);
// При частых вызовах функция будет выполняться максимум раз в секунду
Что проверяется
- Работа с таймерами
- Разница между throttle и debounce
- Управление частотой вызовов функций
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean = false;
return function (...args: Parameters<T>) {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
Как это работает
Принцип работы:
- Флаг
inThrottleблокирует повторные вызовы на времяlimit - После истечения
limitмиллисекунд флаг сбрасывается - Новые вызовы могут быть выполнены
Вызовы функции при частых обращениях:
Время: 0ms 100ms 200ms 300ms 400ms 500ms 600ms 700ms 800ms 900ms 1000ms
Вызовы: X X X X X - - - - - X
Выпол: ✓ ✗ ✗ ✗ ✗ - - - - - ✓
При throttle(fn, 300):
- Первый вызов: ВЫПОЛНИТЬ, установить блокировку на 300мс
- Вызовы на 100-200мс: ИГНОРИРОВАТЬ (в периоде блокировки)
- После 300мс: блокировка снята, следующий вызов ВЫПОЛНИТСЯ
Пример использования
const throttledScroll = throttle(() => {
console.log("Обработка scroll события");
// Выполнится максимум раз в 1000мс
}, 1000);
window.addEventListener('scroll', throttledScroll);
// При быстрой прокрутке событие будет обработано максимум раз в секунду
Версия с сохранением последнего значения
function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean = false;
let lastArgs: Parameters<T> | null = null;
return function (...args: Parameters<T>) {
lastArgs = args;
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
// Если были вызовы во время блокировки, выполнить с последними аргументами
if (lastArgs) {
fn(...lastArgs);
lastArgs = null;
}
}, limit);
}
};
}
Эта версия гарантирует, что функция выполнится с ПОСЛЕДНИМИ аргументами, даже если было много вызовов.
Версия с ведущим и хвостовым вызовом
interface ThrottleOptions {
leading?: boolean; // Выполнить в начале периода (default: true)
trailing?: boolean; // Выполнить в конце периода (default: false)
}
function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number,
options: ThrottleOptions = {}
): (...args: Parameters<T>) => void {
const { leading = true, trailing = false } = options;
let inThrottle: boolean = false;
let lastArgs: Parameters<T> | null = null;
return function (...args: Parameters<T>) {
lastArgs = args;
if (!inThrottle) {
if (leading) {
fn(...args);
}
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (trailing && lastArgs) {
fn(...lastArgs);
lastArgs = null;
}
}, limit);
}
};
}
// Использование:
const scrollHandler = throttle(
() => console.log('Scroll'),
1000,
{ leading: true, trailing: true } // Вызов в начале И в конце
);
Тестирование throttle
import { describe, it, expect, vi } from 'vitest';
describe('throttle', () => {
beforeEach(() => {
vi.useFakeTimers();
});
it('должен выполнить функцию сразу', () => {
const fn = vi.fn();
const throttled = throttle(fn, 1000);
throttled(1);
expect(fn).toHaveBeenCalledWith(1);
expect(fn).toHaveBeenCalledTimes(1);
});
it('должен игнорировать вызовы в периоде throttle', () => {
const fn = vi.fn();
const throttled = throttle(fn, 1000);
throttled(1);
vi.advanceTimersByTime(100);
throttled(2);
vi.advanceTimersByTime(100);
throttled(3);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith(1);
});
it('должен выполнить следующий вызов после истечения limit', () => {
const fn = vi.fn();
const throttled = throttle(fn, 1000);
throttled(1);
vi.advanceTimersByTime(1000);
throttled(2);
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenNthCalledWith(1, 1);
expect(fn).toHaveBeenNthCalledWith(2, 2);
});
});
Throttle vs Debounce
| Аспект | Throttle | Debounce |
|---|---|---|
| Идея | Ограничивает частоту вызовов | Откладывает вызов до затишья |
| Выполнение | Максимум N раз в секунду | Один раз после паузы |
| Использование | Scroll, resize, mousemove | Search input, validation |
| Пример | На каждый 10-й символ | После паузы в 500мс |
Throttle: первый вызов выполняется сразу, остальные блокируются на время limit.
Debounce: все вызовы откладываются, и выполняется только последний через delay после паузы.
Практические применения throttle
- Обработка scroll события — реагировать максимум раз в 100мс
- Обработка resize окна — пересчитывать layout максимум раз в 250мс
- Mouse/Touch события — не обрабатывать более чем раз в 16мс для 60fps
- API запросы — защита от flood, максимум раз в 5 сек
- Автосохранение — сохранять максимум раз в 10 сек
Реальный пример: обработка scroll
const handleScroll = () => {
const scrollPos = window.scrollY;
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = (scrollPos / maxScroll) * 100;
console.log(`Прокручено: ${scrollPercent.toFixed(1)}%`);
};
const throttledScroll = throttle(handleScroll, 250);
window.addEventListener('scroll', throttledScroll);
// handleScroll будет вызван максимум раз в 250мс