Реализуйте функцию throttle(fn, delay, ctx) — «троттлинг», которая возвращает обёртку, вызывающую fn не чаще, чем раз в delay миллисекунд. В качестве контекста исполнения используется ctx. Первый вызов fn всегда должен быть синхронным. Если игнорируемый вызов оказался последним, то он должен выполниться.
Пример
function test() { const start = Date.now(); function log(text) { const msPassed = Date.now() - start; console.log(`${msPassed}: ${this.name} logged ${text}`); } const throttled = throttle(log, 100, { name: "me" }); setTimeout(() => throttled("m"), 0); setTimeout(() => throttled("mo"), 22); setTimeout(() => throttled("mos"), 33); setTimeout(() => throttled("mosc"), 150); setTimeout(() => throttled("moscow"), 400); // Ожидаемый вывод: // 0ms: me logged m // 100ms: me logged mos // 200ms: me logged mosc // 400ms: me logged moscow}
Решение
Оптимальное решение
function throttle(func, delay, ctx) { let lastArgs; let timer; function setTimer() { timer = setTimeout(() => { if (lastArgs) { func.call(ctx, ...lastArgs); lastArgs = null; setTimer(); } else { timer = null; } }, delay); } return function (...args) { if (!timer) { func.call(ctx, ...args); setTimer(); } else { lastArgs = args; } };}
Альтернативное решение (компенсация дрейфа времени)
function throttle(func, delay, ctx) { let lastArgs; let timer; let nextExecution; function setTimer() { const now = Date.now(); // Вычисляем задержку с учётом уже потраченного времени const actualDelay = Math.max(0, nextExecution - now); timer = setTimeout(() => { if (lastArgs) { const executionStart = Date.now(); func.call(ctx, ...lastArgs); lastArgs = null; // Следующее выполнение через delay от начала текущего nextExecution = executionStart + delay; setTimer(); } else { timer = null; nextExecution = null; } }, actualDelay); } return function (...args) { if (!timer) { const executionStart = Date.now(); func.call(ctx, ...args); // Планируем следующее выполнение через delay от НАЧАЛА текущего nextExecution = executionStart + delay; setTimer(); } else { lastArgs = args; } };}