节流(throttle)是指:无论事件多高频触发,在固定时间窗口内最多执行一次。它适合 scroll、resize、拖拽和动画这类需要持续响应的场景【1】【2】【6】【8】。
下面是一个支持首次立即执行(leading)和结束后补一次执行(trailing)的 TypeScript 实现:
type Throttled<T extends (...args: any[]) => void> = {
(...args: Parameters<T>): void;
cancel: () => void;
};
function throttle<T extends (...args: any[]) => void>(
fn: T,
wait = 300,
options = { leading: true, trailing: true }
): Throttled<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
let lastTime = 0;
let lastArgs: Parameters<T> | undefined;
let lastThis: ThisParameterType<T> | undefined;
function invoke() {
lastTime = Date.now();
timer = undefined;
if (lastArgs) {
fn.apply(lastThis, lastArgs);
lastArgs = undefined;
lastThis = undefined;
}
}
function throttled(this: ThisParameterType<T>, ...args: Parameters<T>) {
const now = Date.now();
// 不立即执行时,首次也进入等待
if (!lastTime && !options.leading) {
lastTime = now;
}
const remaining = wait - (now - lastTime);
lastArgs = args;
lastThis = this;
// 已经过了一个时间窗口,立即执行
if (remaining <= 0 || remaining > wait) {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
invoke();
return;
}
// 窗口内触发过多:结束时只补执行最后一次
if (!timer && options.trailing) {
timer = setTimeout(invoke, remaining);
}
}
throttled.cancel = () => {
if (timer) clearTimeout(timer);
timer = undefined;
lastTime = 0;
lastArgs = undefined;
lastThis = undefined;
};
return throttled;
}
scroll 示例:
const handleScroll = throttle(() => {
console.log('当前滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', handleScroll);
默认行为是:
- 第一次触发:立即执行;
- 接下来
200ms内:不重复执行; - 如果期间持续触发:窗口结束后,使用最后一次参数再执行一次。
组件卸载或事件解绑时,记得取消尾部定时器:
window.removeEventListener('scroll', handleScroll);
handleScroll.cancel();