节流函数默认不执行最后一次调用,需通过延迟+重置机制实现“保底执行”;其核心是每次调用保存上下文与参数,并用settimeout配合cleartimeout覆盖旧任务,确保停顿时执行最新一次。

节流函数默认不保证执行最后一次调用,它只控制单位时间内最多执行一次。如果业务需要“在连续触发中确保最后一次一定被执行”,就不能用基础节流,而要引入延迟+重置机制——本质是把节流改造成“带尾部兜底”的混合逻辑。
为什么基础节流不执行最后一次?
标准时间戳节流(如记录 lastTime,只在间隔超时后执行)会在用户停止触发后直接结束,末次调用若未达时间阈值就被丢弃;定时器节流(如用 flag 控制开关)则可能因未触发新定时器而遗漏结尾动作。两者都缺少对“刚结束但未满足条件”这一状态的响应。
实现“保底执行最后一次”的关键结构
必须同时管理两个要素:当前触发的时间/参数、以及一个可被反复清除并重建的延迟任务。核心不是“限制频率”,而是“延迟执行,并始终让最新一次覆盖旧任务”。
- 每次调用都保存 arguments 和 this 上下文
- 用 setTimeout 设置延迟执行,但每次新调用都 clearTimeout 旧任务
- 延迟时间到时,用保存的最新参数执行原函数
- 无需判断时间差,只依赖定时器重置逻辑
一个可靠可用的实现示例
以下代码兼顾上下文、参数传递和清除安全,适用于 resize、mousemove 等高频场景:
function throttleLast(fn, delay) {
let timeoutId = null;
let lastArgs = [];
let lastThis = null;
return function(...args) {
lastThis = this;
lastArgs = args;
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fn.apply(lastThis, lastArgs);
timeoutId = null;
}, delay);
};
}
调用方式不变:window.addEventListener('resize', throttleLast(handleResize, 150))。只要在 delay 内有新触发,旧任务就取消,新任务延后执行——最终停顿时,最后一次参数必然送达。
和防抖的区别不能混淆
这个方案看起来像防抖,但目的不同:防抖是“等静默后再执行”,适用于搜索建议、校验等;而“节流保最后”仍是节流语义——它不追求静默期,而是确保高频流中的收尾动作不丢失,比如拖拽结束时需提交最终坐标、滚动到底部需加载下一屏。两者适用场景有明确分界。











