节流函数必须依赖闭包,因其需私有、持久、可复用地维护lasttime或timer状态;时间戳版首次立即执行、适合稳定采样,定时器版末次执行、适合输入搜索;leading/trailing控制首尾触发,需注意this、参数传递及事件移除细节。

节流函数的核心目的,是让高频触发的事件(比如滚动、鼠标移动、窗口缩放)按固定间隔执行回调,而不是每次触发都响应。它不是靠“阻止”来实现限制,而是靠“状态管理”——而这个状态,必须私有、持久、可复用,闭包正是唯一自然满足这三点的机制。
为什么节流必须依赖闭包
节流需要记住两个关键信息:上一次真正执行的时间(lastTime),或当前待执行的定时器 ID(timer)。这些值不能放在全局,否则多个节流实例(比如两个滚动监听器)会互相覆盖;也不能放在每次调用内部,否则状态无法延续。
- 闭包让
throttle(fn, delay)每次调用都生成独立的作用域,各自持有自己的lastTime或timer - 外部无法访问这些变量,避免污染和误改
- 函数返回的“包装后”的处理函数,持续引用着外层变量,使其生命周期延长到整个使用周期
时间戳版 vs 定时器版:行为差异决定选型
两种主流实现方式,底层逻辑不同,适用场景也不同,混用容易出错。
-
时间戳版:首次触发立即执行,之后在
delay内的所有调用都被忽略,直到时间差达标才再次执行。适合 resize、mousemove 这类需要“稳定采样”的场景 - 定时器版:首次触发设定时器,delay 后执行;期间重复触发会清除旧定时器、重设新定时器,最终只执行最后一次。适合输入框搜索、按钮点击这类“以末次为准”的操作
- 错误做法:在时间戳版里加
clearTimeout,或在定时器版里直接比对lastTime,会导致执行漏掉或重复
leading 和 trailing:如何控制首尾执行
原生节流不提供开关,但业务常需更精细的控制。例如拖拽要立刻响应(leading: true),列表滚动到底要松手后补一次加载(trailing: true)。
- 开 leading:第一次调用直接执行,并更新
lastTime - 开 trailing:必须基于定时器实现,每次调用都尝试设置新定时器,确保只保留最后一次
- trailing 回调里要再判断:若距上次执行已超
delay,就跳过,避免与 leading 冲突 - 漏掉
clearTimeout或错判lastTime更新时机,会导致 trailing 不触发或重复触发
实际应用中的关键细节
节流不是套个函数就能用好,几个细节常被忽略:
- 函数上下文(
this)和参数要正确传递,推荐用func.apply(this, arguments)或展开语法 - 如果节流用于事件监听器,注意移除时要传入原始包装函数,而非原函数
- 移动端 touchmove 等事件同样适用,但要注意
passive: false配合,避免浏览器自动禁用 preventDefault - 调试时可在回调中打印时间戳,验证是否真按预期间隔执行











