闭包是JavaScript中函数与其捕获的词法环境捆绑的运行时现象;它实现私有状态、缓存、事件上下文保持和柯里化,但易致内存泄漏,需通过清理引用、限制缓存、事件委托等手段安全使用。

闭包 是 JavaScript 中一个函数和它所捕获的词法环境(也就是定义时所在作用域中的变量)捆绑在一起的组合。它不是语法结构,而是一种运行时现象:只要内层函数引用了外层函数的局部变量,并且这个内层函数在外部被调用,就形成了闭包。
闭包怎么形成?看一个最简例子
下面这段代码里,inner 就是一个闭包:
function outer() {
const count = 0;
return function inner() {
return ++count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2outer 执行完后本该销毁 count,但 inner 还“记得”它,所以 count 持续存活——这就是闭包的本质。
闭包常用于哪些真实场景?
它不是炫技工具,而是解决具体问题的手段:
立即学习“Java免费学习笔记(深入)”;
-
封装私有状态:比如模块导出一个计数器,但不让外部直接改
count值 -
缓存计算结果:如
memoize函数,用闭包保存Map或WeakMap缓存 -
事件回调中保持上下文:循环绑定点击事件时,用闭包存
i或item.id,避免所有回调都用最后一个值 -
柯里化与偏应用:如
const add5 = add(5),靠闭包锁住第一个参数
闭包最大的风险是内存泄漏,不是“写法错”,而是“忘了清理”
闭包本身不危险,危险的是它让变量“赖着不走”。常见踩坑点:
- 给大量 DOM 元素绑定事件,每个都用闭包存整个
element或dataset,却没在元素移除时调用removeEventListener - React/Vue 组件卸载后,
setInterval回调里的闭包仍强引用组件实例(this或ref),导致整棵树无法回收 - 缓存函数(如
memoize)无上限增长,且缓存项本身又含闭包,形成引用链闭环 - 单例模块中用闭包缓存大对象(如
JSON.parse后的完整响应体),后续不再需要却没手动设为null
判断是否泄漏:打开 Chrome DevTools → Memory 面板 → 录制堆快照,筛选 Closure 类型,看是否有异常增长的闭包对象及其引用链。
怎么安全地用闭包?三条实操建议
不是不用,而是有意识地控制生命周期:
- 事件监听器优先用事件委托,避免逐个绑定;必须绑定时,用
WeakMap存关联数据,而非闭包持引用 - 定时器、网络请求等异步操作,统一在组件卸载/销毁时清理:
clearInterval(id)、abortController.abort()、removeEventListener - 缓存类闭包加限制:比如
memoize(fn, { max: 100 }),或用Map+setTimeout实现 LRU 过期机制
闭包的复杂性不在语法,而在它把“变量何时释放”从自动变成了你得亲手过问的事。











