节流函数必须用闭包,因其需私有持久化lasttime或timer状态变量;时间戳版首次立即执行、保频稳定,定时器版延迟执行最后一次、适合收尾场景,二者逻辑冲突不可混用。

节流函数的核心是控制函数执行频率,而闭包不是可选项,是实现它的必要基础——它让状态变量(如上一次执行时间或定时器 ID)能在多次调用中私有、持久地存在,又不被外部干扰。
为什么必须用闭包?
节流需要两个关键状态:
- lastTime:记录上一次真正执行回调的时间戳,用于判断是否已过间隔期
- timer:保存 setTimeout 返回的 ID,用于清除待执行的延迟任务
这两个值不能每次调用都重置,也不能暴露给全局作用域。闭包天然满足:外层函数定义变量,内层返回的函数“记住”它们,形成独立作用域。不用闭包,就只能靠全局变量或对象属性,结果是多实例覆盖、竞态出错、组件卸载后状态残留。
时间戳版:首次立即执行,适合滚动/缩放监听
每次触发读取 Date.now(),与闭包内私有的 lastTime 比较;差值达标才执行并更新 lastTime。逻辑轻量,无定时器开销,保频稳定。
示例写法:
function throttle(func, delay) {let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
定时器版:延迟执行最后一次,适合输入搜索/拖拽收尾
每次触发都尝试设 setTimeout;若已有 timer 就先 clearTimeout,再重设。最终只执行最后一次设定的任务。停止触发后会补一次,但首次不立即执行。
示例写法:
function throttle(func, delay) {let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}
不能混用两种逻辑
有人想“既首次立刻执行,又停手后补一次”,于是把 lastTime 和 timer 塞进同一个函数。结果是判断条件冲突:时间戳逻辑看“是否过了间隔”,定时器逻辑看“是否清除重设”。状态互相影响,容易漏执行或重复执行。闭包能同时存两个变量,不代表它们该协同工作——选一种语义清晰的策略,比堆砌状态更可靠。











