节流函数必须用闭包以隔离lasttime或timer状态,避免污染全局、多实例干扰;时间戳版首次触发即执行,靠lasttime控频;定时器版延迟执行且停触后补一次;二者逻辑冲突不可混用。

节流函数必须用闭包,不是为了炫技,而是因为 lastTime 和 timer 这两个状态变量需要被多次调用“记住”,又不能被外部访问或多个实例互相干扰。没有闭包,就只能靠全局变量或对象属性,结果是污染作用域、状态错乱、多监听器打架。
时间戳版:靠 lastTime 控制“节奏”
每次触发时读取当前时间戳,和上一次真正执行的时间比较。差值够了,就立即执行并更新 lastTime;不够,就跳过。
- 首次触发立刻执行,适合 scroll、resize 这类要“稳频响应”的场景
- 不依赖定时器,无 clearTimeout 开销,性能更轻量
- 闭包唯一要保管的,就是那个私有的 lastTime 变量——它在函数返回后持续存在,且每个 throttle 实例互不共享
定时器版:靠 timer 管理“延迟承诺”
每次触发都尝试设一个 setTimeout,如果已有 timer 就先清除再重设。最终只执行最后一次设下的定时任务。
- 首次触发不执行,要等 delay 后才执行;停止触发后还会补一次,适合输入搜索、拖拽结束后的收尾动作
- 闭包必须封存 timer(即 setTimeout 返回的 ID),否则无法安全清除旧任务
- 若漏掉闭包封装,多个 throttle 调用会共用同一个 timer,导致前一个还没执行就被后一个清掉
为什么不能混着写?
有人想“既首调立即执行,又保证停手后补一次”,于是把 lastTime 和 timer 全塞进一个函数里。结果是逻辑冲突:
- 时间戳逻辑靠“是否过了间隔”决定是否执行,而定时器逻辑靠“是否清除重设”控制执行时机
- 两者对“该不该执行”的判断条件不同,状态变量互相影响,容易漏执行或重复执行
- 闭包虽然能同时存 lastTime 和 timer,但不代表它们该在同一套节流逻辑里协同工作
闭包不是可选项,是隔离层
每个 throttle(fn, 100) 调用都应该生成一个独立作用域:
- 这个作用域里有自己的 lastTime 或 timer,和其他 throttle 实例完全无关
- 组件卸载、监听器解绑后,闭包引用消失,状态自然回收,不会残留
- 你可以在同一页面给滚动、缩放、按钮点击各配一个 throttle,彼此零干扰











