闭包是JavaScript中函数作用域与词法环境自然结合的必然结果;其形成依赖函数的[[Environment]]属性和自由变量查找机制,用于封装私有状态、保持异步上下文,并需警惕内存泄漏。

闭包不是语法糖,也不是高级技巧——它是 JavaScript 中函数作用域和词法环境自然结合的必然结果。只要一个函数在定义它的词法作用域之外被调用,且它引用了该作用域中的变量,闭包就产生了。
闭包是怎么形成的:看懂 [[Environment]] 和自由变量
每个函数对象内部都有一个隐藏属性 [[Environment]],它指向函数定义时所在的作用域链。当函数执行时,如果访问了自己作用域中未声明的变量(即“自由变量”),引擎就会顺着 [[Environment]] 一层层向上查找——这个被保留下来的外层词法环境,就是闭包的核心。
常见错误现象:
- 循环中用
var声明变量 + 异步回调,所有回调都输出最后一个值(因为共享同一个变量绑定) - 误以为
setTimeout的回调“捕获了当前值”,其实捕获的是变量引用
正确做法是让每次迭代拥有独立作用域:
立即学习“Java免费学习笔记(深入)”;
for (var i = 0; i < 3; i++) {
(function(i) {
setTimeout(() => console.log(i), 100);
})(i);
}
// 或更现代的写法:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
let 每次迭代都会创建新绑定,[[Environment]] 指向各自独立的块级环境,本质仍是闭包。
封装私有状态:用闭包替代 class 私有字段(尤其兼容旧环境)
ES6 class 的 #private 字段在 IE 和部分老版本 Node.js 中不可用,而闭包在任何支持函数的一版 JS 中都有效。
使用场景:
本文档主要讲述的是Android的资源与国际化设置;资源是外部文件(不含代码的文件),它被代码使用并在编译时编入应用程序。Android支持不同类型的资源文件,包括XML,PNG以及JPEG文件XML文件根据描述的不同有不同格式。这份文档描述可以支持什么样的文件,语法,以及各种格式。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
- 模块导出 API 时隐藏实现细节(如缓存、计数器、配置)
- 避免全局污染或意外修改关键状态
- 需要精细控制 getter/setter 行为(比如带校验的 setter)
示例:
function createCounter() {
let count = 0; // 外部无法直接访问
return {
increment() { count++; },
get value() { return count; },
reset() { count = 0; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.value); // 1
console.log(counter.count); // undefined —— 真正私有
注意:这里返回的对象方法都闭包了 count,但对象本身没有暴露该变量。
事件监听与定时器中保持上下文:为什么 this 不是唯一问题
很多人只记得用 .bind() 或箭头函数解决 this 绑定,却忽略另一个更隐蔽的问题:依赖的局部变量可能已变更或销毁。
典型场景:
- 组件卸载后,异步回调仍尝试更新已销毁的 DOM 或 React state
- 轮询请求中,用户切换页面,旧的
id或token仍被后续响应使用
闭包能帮你“冻结”那一刻所需的最小数据集:
function startPolling(id, token) {
const poll = () => {
fetch(`/api/data?id=${id}`, {
headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json())
.then(data => updateUI(data));
};
const timer = setInterval(poll, 5000);
// 返回清理函数,利用闭包捕获 timer 和 id
return () => clearInterval(timer);
}
const stop = startPolling(123, 'abc');
// 后续可安全调用 stop(),无需再传参数
这里 stop 函数闭包了 timer,而不是靠外部变量管理——这是健壮性的关键。
闭包真正的复杂点不在“怎么写”,而在“什么时候不该用”:过度闭包会阻止垃圾回收,导致内存泄漏;在频繁创建函数的循环里(如渲染大量列表项),每个闭包都携带一份环境引用,开销比想象中大。判断依据很简单——只闭包真正需要的数据,其余尽量通过参数传入。









