节流的核心逻辑是“固定时间窗口内最多执行一次”,保证函数在指定时间间隔内至少执行一次,典型用于scroll、mousemove事件;时间戳方案最简可靠,用Date.now()比对lastTime实现,避免setTimeout累积定时器导致的响应丢失。

节流的核心逻辑是“固定时间窗口内最多执行一次”
节流(throttle)不是防抖(debounce),它不等待停止触发才执行,而是保证函数在指定时间间隔内**至少执行一次**。典型场景是 window.onscroll、mousemove 事件监听——你希望每 100ms 最多处理一次,而不是等用户停下才响应。
用 setTimeout + 时间戳实现最简可靠节流
这是兼容性最好、逻辑最直白的写法,不依赖定时器叠加或闭包状态管理,避免了 setInterval 带来的同步风险和清理遗漏问题。
- 记录上一次执行的时间戳
lastTime - 每次调用时,计算当前时间与
lastTime的差值 - 差值 ≥ 间隔(如
delay),则立即执行,并更新lastTime;否则不执行
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
为什么不用 setTimeout + clearTimeout 模拟节流?
这种写法常见但容易误用为防抖:它本质是“延迟执行并不断重置”,若用户持续高频触发,函数可能**永远不执行**——这违背节流“保底执行”的设计目标。
-
setTimeout节流版本会累积定时器,需手动clearTimeout,状态管理复杂 - 无法保证“每 delay ms 至少触发一次”,只保证“最后一次触发后 delay ms 执行”
- 在滚动场景中,用户快速拖动时,页面可能完全无响应,体验更差
注意 this 和参数传递的完整性
原生事件回调中的 this 指向触发元素(如 div),而节流函数返回的新函数默认丢失该绑定。必须用 fn.apply(this, args) 或 fn.call(this, ...args) 显式还原上下文。
立即学习“Java免费学习笔记(深入)”;
- 漏掉
...args会导致事件对象(如event)丢失,常见于addEventListener场景 - 若需访问原生事件,确保监听时未使用
passive: true(否则event可能为只读或不可用) - 不建议在节流函数内部直接修改
event属性,因节流可能跨多次事件调用,状态易混乱
节流看似简单,真正难的是在「不丢事件语义」和「不破性能底线」之间拿捏分寸——时间戳方案胜在确定性,而任何引入异步延迟的变体,都可能悄悄把“保底执行”变成“看运气执行”。











