闭包是JavaScript中内部函数访问外部变量并逃逸执行时自动形成的运行时行为;需同时满足函数嵌套、变量捕获、逃逸执行三条件;用于封装私有状态、解决循环索引问题,但可能引发内存泄漏。

闭包 是 JavaScript 中函数“记住自己出生地”的能力:当一个函数被定义在另一个函数内部,并且**访问了外部函数的变量**,同时这个内部函数**被返回或传到别处执行**,它就形成了闭包。
它不是语法糖,也不是设计模式,而是一种运行时行为——只要满足这三个条件,闭包就自动产生。
怎么一眼识别闭包?看这三步
闭包不是写出来的,是“跑出来”的。判断一个函数是不是闭包,只看三点:
- 函数嵌套:内部函数定义在外部函数体内
- 变量捕获:
inner函数体里直接用了outer的局部变量(比如let count、const config) - 逃逸执行:这个
inner被返回、赋值给变量、传给setTimeout或事件监听器,**脱离了原始调用环境**
缺一不可。比如下面这个就不是闭包:
function outer() {
const x = 1;
function inner() {
console.log('I ignore x'); // 没引用外部变量
}
return inner;
}
inner 虽然嵌套,但没捕获任何自由变量,引擎不会为它保留作用域链。
最常用的实际用途:封装私有状态
JavaScript 没有 #private 字段(ES2022+ 才有,且不兼容旧环境),而闭包是长期稳定、全版本兼容的私有变量方案。
立即学习“Java免费学习笔记(深入)”;
典型写法:
function createCounter() {
let count = 0; // 外部变量 → 被闭包保护
return {
increment() { count++; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1
这里 count 对外完全不可见,连 counter.count 都是 undefined。所有修改必须走暴露的方法。
- 避免全局污染:变量锁死在函数作用域内
- 防止误改:外部无法绕过逻辑直接赋值
- 适合配置模块、单例缓存、状态管理器等场景
循环中绑定正确索引:别再用 var 了
这是新手踩坑最多的地方。下面这段代码会输出 5 个 5:
本文档主要讲述的是Android的资源与国际化设置;资源是外部文件(不含代码的文件),它被代码使用并在编译时编入应用程序。Android支持不同类型的资源文件,包括XML,PNG以及JPEG文件XML文件根据描述的不同有不同格式。这份文档描述可以支持什么样的文件,语法,以及各种格式。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
因为 var 是函数作用域,i 全局共享;所有回调共用同一个 i,等真正执行时循环早已结束,i === 5。
用闭包修复(ES5 方式):
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
更现代、更推荐的写法(ES6+):
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
let 声明每次迭代都创建新绑定,本质就是语言层帮你做了闭包封装——你不用手动写 IIFE,但原理一样。
容易被忽略的代价:内存泄漏风险
闭包让变量“活”得更久——只要内部函数还存在,它引用的所有外部变量就无法被垃圾回收。
- 如果闭包持有 DOM 节点、大数组、或整个对象树,而你又忘了清理(比如没移除事件监听器),内存就悄悄涨上去了
- 在长生命周期对象(如页面级模块)中频繁创建闭包,尤其要检查是否无意保留了对大对象的引用
- 调试时可借助 Chrome DevTools 的 Memory > Heap Snapshot,筛选
Closure类型,看哪些变量被意外滞留
闭包本身不是问题,问题在于“该释放时不释放”。它像一把没有保险栓的刀——好用,但得清楚自己握着哪一头。










