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

闭包不是语法糖,也不是高级技巧——它是 javascript 中函数作用域和词法环境自然结合的必然结果。只要一个函数在定义它的词法作用域之外被调用,且仍能访问该作用域中的变量,那就构成了闭包。
闭包是怎么形成的?看 function 和 [[Environment]] 的绑定
每个函数对象内部都隐式保存着一个 [[Environment]] 内部属性,指向它被定义时所处的词法环境(比如外层函数的 LexicalEnvironment)。这个绑定在函数创建时就固定了,不会随调用位置改变。
常见错误现象:以为“返回函数”才叫闭包,其实只要函数能读取外层变量,哪怕没返回、没暴露,也已是闭包。
封装私有状态:用闭包替代 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 声明的循环变量在闭包中会共享同一个绑定,导致所有回调看到最后的值:
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 可以查哪些闭包持有了不该持有的对象。