节流函数要确保最后一次调用执行,关键在于引入尾部执行逻辑:缓存最后一次参数和上下文,通过定时器延迟执行而非仅依赖时间戳比对,避免遗漏末次触发。

节流函数中要让最后一次调用也执行,关键是在“停止触发后”补上一次延迟执行——即在定时器清除前,记录最后一次的参数和上下文,并在定时器到期时执行它。单纯使用 setTimeout + 清除机制(如常见节流)默认会丢弃最后一次未到时间窗口的调用;要保留它,需引入“尾部执行”逻辑。
核心思路:标记最后一次调用并延迟执行
不是每次触发都重置定时器,而是:
- 首次触发时启动定时器,设置一个延时任务(比如 200ms 后执行)
- 后续触发不立即执行,但更新待执行的参数/上下文,并**不清除定时器**(或清除后立刻重新设一个)
- 当触发停止,定时器最终运行时,执行的是**最后一次保存的调用信息**
实现方式:使用「定时器 ID + 参数缓存」
下面是一个带尾部执行的节流函数示例(使用定时器,非时间戳比对):
function throttle(func, delay) {
let timeout = null;
let lastArgs = null;
let lastThis = null;
<p>function run() {
if (lastArgs === null) return;
func.apply(lastThis, lastArgs);
lastArgs = null;
lastThis = null;
}</p><p>return function throttled(...args) {
lastThis = this;
lastArgs = args;</p><pre class="brush:php;toolbar:false;">if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
run();
}, delay);
}}; }
✅ 特点说明:
- 每次调用都更新
lastThis和lastArgs - 只在定时器空闲时才启动新定时器(
!timeout),避免重复设时 - 定时器回调里执行的是最后一次保存的调用,执行完清空缓存
- 即使用户快速连续触发多次,最后停顿
delay时间后,一定执行最后一次
对比:为什么常见节流(如防抖式节流)不执行最后一次?
很多节流写法是「固定间隔执行」,例如:
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}这种是「时间戳节流」,它保证最小间隔,但最后一次若没达到间隔就不执行——因为没有延迟兜底机制。而定时器方案通过异步延迟+缓存,天然支持尾部执行。
可选增强:支持立即执行 + 尾部执行(双触发)
如果还想第一次触发立即执行(类似 lodash 的 {leading: true, trailing: true}),可扩展为:
function throttle(func, delay, options = {}) {
const { leading = false, trailing = true } = options;
let timeout = null;
let lastArgs = null;
let lastThis = null;
let isLeadingExecuted = false;
<p>function invoke() {
func.apply(lastThis, lastArgs);
lastArgs = null;
lastThis = null;
if (trailing) isLeadingExecuted = false;
}</p><p>function startTimer() {
timeout = setTimeout(() => {
timeout = null;
if (lastArgs !== null) invoke();
}, delay);
}</p><p>return function(...args) {
lastThis = this;
lastArgs = args;</p><pre class="brush:php;toolbar:false;">if (leading && !isLeadingExecuted) {
func.apply(this, args);
isLeadingExecuted = true;
startTimer();
} else if (!timeout) {
startTimer();
}}; }
这样就能做到:首次立即执行 + 停止后确保末次再执行一次。


















