闭包变量能长期驻留内存,是因为返回的防抖函数持续引用外层作用域中的timer等变量,只要该函数未被解除绑定或置为null,gc就不会回收其依赖的闭包作用域。

防抖函数里的闭包变量能长期驻留内存,关键在于“内部函数持续持有对外部作用域的引用”,而 JavaScript 的垃圾回收机制(GC)不会回收仍被活跃引用的对象。
闭包变量不被回收的根本原因
防抖函数通常返回一个新函数,这个函数内部会访问并操作外层定义的变量(比如 timer 或 lastTime)。只要这个返回的函数还被外部变量引用(例如绑定在事件监听器上),它就一直存活;而它存活,就必然维持着对父级作用域中变量的引用——这些变量因此无法被 GC 清理。
- 外层函数执行完毕后,其执行上下文本该销毁,但因内层函数通过作用域链“抓着”它的活动对象(AO),AO 被保留下来
- timer 等变量就存在这个 AO 里,随 AO 一起被持久化
- 只要防抖函数返回的 handler 还没被解除绑定、也没被赋值为 null,闭包就一直有效
典型防抖代码中的闭包结构
看这段标准实现:
function debounce(fn, delay) {
let timer = null; // ← 闭包变量,定义在外层函数作用域
return function(...args) {
clearTimeout(timer); // ← 访问并修改外层 timer
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
每次调用 debounce 都生成一个独立闭包:其中 timer 是私有状态,只对该返回的 handler 可见。多个防抖实例互不影响,正是靠每个闭包各自持有一份 timer。
什么时候闭包变量会被释放?
闭包变量不是永久不销毁,而是取决于引用是否断开:
- 事件监听器被 removeEventListener 移除,且没有其他变量引用该 handler
- 手动将 handler 赋值为 null,切断引用链
- 所在组件或对象被销毁(如 Vue 组件 beforeUnmount 中清理)
一旦 handler 不再可达,JS 引擎会在下一次垃圾回收周期中,连带回收它所依赖的整个闭包作用域及其中的变量。
为什么不用主动清空 timer?
大多数场景下不需要手动设 timer = null。因为:
- clearTimeout(timer) 本身不释放内存,但 timer 值被覆盖后,旧定时器 ID 失去意义
- 真正影响内存的是 handler 是否存活,而不是 timer 当前值是 null 还是数字
- 只有长期驻留且不再使用的闭包(比如全局监听未清理)才需主动干预











