节流必须用闭包来私有持久保存lasttime和timer状态,避免全局污染与实例干扰;时间戳版首次立即执行、适合滚动监听,定时器版延迟执行最后一次、适合输入搜索,增强版支持leading/trailing需闭包统一管理状态。

节流的核心是控制函数执行频率,而闭包是实现它的必要手段——因为必须在多次调用之间私有、持久地保存状态(比如上一次执行时间或定时器 ID),又不能让这些状态暴露或互相干扰。
为什么非得用闭包?
节流需要两个关键状态:
- lastTime:记录上一次真正执行回调的时间戳,用于判断是否已过间隔期
-
timer:保存
setTimeout返回的 ID,用于清除待执行的延迟任务
这两个值每次调用都不能重置,也不能被外部修改。闭包天然满足:外层函数定义变量,内层返回函数“记住”它们,形成独立作用域。不用闭包,就只能靠全局变量或对象属性,会导致多实例覆盖、竞态问题,甚至组件卸载后状态残留。
时间戳版:适合滚动监听这类保频场景
首次触发立刻执行,之后在 delay 内的调用全部忽略,直到间隔过去才再次响应。逻辑清晰,无定时器依赖,适合 scroll、resize 等需“稳定节奏”的高频事件。
示例写法:
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
定时器版:适合“松手后执行”类交互
首次触发设一个延时任务,delay 后执行;期间重复触发会清除旧定时器、重设新的,最终只执行最后一次。适合搜索框输入后发请求、滚动到底部加载更多等场景。
关键点:
- 必须用
clearTimeout清除已有 timer - timer 要在闭包中声明并复用,不能每次新建
- 执行回调后记得清空 timer,避免状态滞留
示例写法:
function throttle(func, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}
带 leading / trailing 的增强写法
真实业务常需要更精细控制:
- leading: true → 首次调用立即执行(如拖拽开始立刻响应)
- trailing: true → 最后一次调用后补执行一次(如滚动结束再加载)
注意:trailing 必须配合定时器;若同时开 leading 和 trailing,trailing 回调里要加判断 if (now - lastTime >= delay),防止和 leading 重复执行。
这种组合能力,只有靠闭包封装 lastTime、timer、options 等状态才能干净实现,每个 throttle 实例互不干扰。










