闭包是JavaScript中函数作用域与词法环境自然结合的必然结果;函数创建时绑定[[Environment]]指向定义时的词法环境,只要能访问外层变量即构成闭包,无论是否返回。

闭包不是语法糖,也不是高级技巧——它是 JavaScript 中函数作用域和词法环境自然结合的必然结果。只要一个函数在定义它的词法作用域之外被调用,且仍能访问该作用域中的变量,那就构成了闭包。
闭包是怎么形成的?看 function 和 [[Environment]] 的绑定
每个函数对象内部都隐式保存着一个 [[Environment]] 内部属性,指向它被定义时所处的词法环境(比如外层函数的 LexicalEnvironment)。这个绑定在函数创建时就固定了,不会随调用位置改变。
常见错误现象:以为“返回函数”才叫闭包,其实只要函数能读取外层变量,哪怕没返回、没暴露,也已是闭包。
-
setTimeout回调里访问外层循环变量,是闭包(但常因变量复用出 bug) - 事件监听器中引用组件状态,是闭包
- 模块模式中
return { get, set },是闭包
封装私有状态:用闭包替代 class 里的 #private
在不支持私有字段的老环境(如 IE、部分 Node.js 版本),闭包是唯一可靠的私有数据方案。变量被封闭在外部函数作用域内,外界无法直接访问。
立即学习“Java免费学习笔记(深入)”;
function createCounter() {
let count = 0; // 外部无法触达
return {
increment() { count++; },
value() { return count; }
};
}
const c1 = createCounter();
c1.increment();
console.log(c1.value()); // 1
注意:count 不是存在 c1 对象上,而是藏在 c1.increment 和 c1.value 共享的闭包环境中。每次调用 createCounter() 都生成独立的闭包,互不影响。
避免循环中闭包捕获同一变量的坑:用 let 或立即执行函数
传统 var 声明的循环变量在闭包中会共享同一个绑定,导致所有回调看到最后的值:
家电公司网站源码是一个以米拓为核心进行开发的家电商城网站模板,程序采用metinfo5.3.9 UTF8进行编码,软件包含完整栏目与数据。安装方法:解压上传到空间,访问域名进行安装,安装好后,到后台-安全与效率-数据备份还原,恢复好数据后到设置-基本信息和外观-电脑把网站名称什么的改为自己的即可。默认后台账号:admin 密码:132456注意:如本地测试中127.0.0.1无法正常使用,请换成l
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
修复方式:
- 改用
let(块级作用域,每次迭代新建绑定) - 用 IIFE 显式传入当前值:
(function(i) { setTimeout(() => console.log(i), 100); })(i) - 用
for...of+ 解构或数组方法(如[0,1,2].forEach(i => setTimeout(...)))
本质不是“闭包有问题”,而是对变量绑定机制理解不到位。
内存与调试:闭包让变量无法被 GC,但别盲目“优化”
闭包会延长外层变量的生命周期——只要闭包函数还存活,它引用的外层变量就不会被垃圾回收。这在长期运行的单页应用中可能引发内存泄漏(比如监听器未解绑,却持续引用大对象)。
但反过来,不要因为“闭包占内存”就回避它。现代 JS 引擎(V8)对闭包做了大量优化,比如只保留实际被引用的变量,而不是整个外层作用域。
真正该检查的是:是否无意保留了不需要的引用?比如把整个 this 或 DOM 节点塞进闭包,又忘了清理。
Chrome DevTools 的 Memory > Heap Snapshot 可以查哪些闭包持有了不该持有的对象。








