防抖和节流均依赖闭包维持状态,但变量类型、更新时机与逻辑不同:防抖仅保存timer用于取消重设,实现“等停稳再执行”;节流保存lasttime或canrun以控制执行节奏,二者不可混用。

防抖和节流都靠闭包维持状态,但各自锁住的变量不同、更新时机不同、执行逻辑也不同——这直接决定了它们适用的场景和行为表现。
防抖闭包:只留一个 timer,每次触发都重置倒计时
防抖函数返回的内层函数必须持续访问外层定义的 timer 变量。这个变量不是每次调用都新建,而是被闭包“捕获”并长期持有。只要 timer 存在,就能被 clearTimeout 正确清除;一旦清掉旧任务、设上新定时器,就实现了“等停稳再执行”的效果。
- 闭包中保存的是单个定时器 ID(如
let timer = null) - 每次事件触发,都先
clearTimeout(timer),再setTimeout(...) - 函数体不关心“上次啥时候执行”,只关心“上一个定时器还在不在”
- 如果不用闭包,timer 每次都是局部变量,clearTimeout 就找不到目标,防抖失效
节流闭包:要记 lastTime 或 canRun,判断是否“该轮到我了”
节流需要跨多次调用感知时间或状态,所以闭包里保存的通常是 lastTime(时间戳法)或 canRun(开关标志法)。前者靠时间差决定能否执行,后者靠布尔值控制入口闸门——无论哪种,都依赖闭包让变量不随调用结束而销毁。
- 时间戳版闭包保存
let lastTime = 0,每次调用比对Date.now() - lastTime >= interval - 开关版闭包保存
let canRun = true,执行前检查,执行后设 false,定时器结束再设回 true - 两种实现都要求变量在多次事件回调间共享,否则每次都是“全新开始”,节流退化为普通调用
- 闭包在这里不是可选项,是机制成立的前提:没有它,就等于没有记忆
为什么不能共用一套闭包结构?
因为防抖和节流解决的是两类问题:一个是“等静止”,一个是“控节奏”。防抖不需要知道上次啥时候执行,只需要取消未完成的任务;节流必须知道上次执行时间或当前是否允许执行,否则无法判断“间隔够不够”或“门开没开”。变量用途不同,初始化方式不同,更新位置也不同——比如 lastTime 在执行后立刻更新,timer 却在设新定时器时才赋值。
- timer 是“待取消对象”,生命周期由 clearTimeout 和 setTimeout 共同管理
- lastTime 是“时间锚点”,只在函数真正执行后才刷新
- canRun 是“通行许可”,在进入函数时置 false,离开时(通过 setTimeout)再置 true
- 混用会导致逻辑错乱:拿 timer 当时间判断依据,或用 lastTime 去 clearTimeout,都不成立
实际写法中闭包的体现很直接
看函数签名就能识别闭包角色:debounce(fn, delay) 返回一个函数,这个返回函数内部访问了外层的 timer;throttle(fn, interval) 同样返回一个函数,它访问的是外层的 lastTime 或 canRun。这些变量不出现在参数里,也不挂全局,全靠作用域链从外层“借来”——这就是闭包最朴素也最必要的用法。
- 所有标准实现都把状态变量声明在外层函数体内,而非内层事件回调中
- 返回的函数哪怕被赋给不同变量、绑定到不同元素,各自仍拥有独立的闭包环境
- 多个 debounce 调用互不影响,正因每个都生成了自己的 timer 闭包
- 闭包不是炫技,是让“有状态的无状态函数”成为可能的基础设施
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











