闭包本质是函数对象对其定义时词法环境([[Environment]])的持久引用,导致外层变量无法被垃圾回收。它不保存值而是绑定引用,所有闭包共享同一变量实例,赋值后实时可见。

闭包不是“函数记住了外层变量”,而是函数对象在创建时绑定的词法环境([[Environment]])被持续持有,导致外层作用域无法被垃圾回收。
闭包的本质是 [[Environment]] 的持久引用
每个函数对象内部都隐式持有一个 [[Environment]] 内部槽,指向其定义时所在词法环境(即作用域链的起点)。这个引用不会因为函数执行结束而消失——只要函数对象还存活,它所捕获的外层变量就仍被引用,无法被 GC 回收。
常见错误现象:
反复绑定事件回调却没清理,导致 DOM 节点和外层变量长期驻留内存;用循环生成多个闭包却误以为每次迭代都新建了独立作用域。
-
var声明的循环变量会被所有闭包共享(因只有一个变量绑定) -
let声明则为每次迭代创建独立绑定,每个闭包捕获各自的i - 箭头函数不创建自己的
this或arguments,但照常继承外层[[Environment]]
如何验证闭包是否真的持有了变量?
用 Chrome DevTools 的 Memory 面板拍快照,或在函数内添加 console.log(outerVar) 并检查 outerVar 是否仍可访问——但这只是表象。真正关键的是:该变量是否出现在 console.dir(fn) 的 [[Scopes]] 中(需开启“Show scopes”选项)。
立即学习“Java免费学习笔记(深入)”;
使用场景:
模块模式、防抖/节流函数、私有状态封装、异步回调中保留上下文。
- 调试时右键函数 → “Reveal in Scope Chain” 可直观看到
Closure作用域 - 若变量未被函数体实际读取,现代 V8 可能优化掉该绑定(如只写不读)
-
eval()或with会强制关闭优化,使所有外层变量都进入闭包
function 和 => 在闭包行为上有没有区别?
没有本质区别。两者都按词法定位并绑定外层 [[Environment]]。差异仅在于 this、arguments、new.target 的绑定方式,不影响变量捕获逻辑。
参数差异:function 有自己的 arguments 对象,而 => 继承外层的;但这不改变闭包变量的生命周期。
- 嵌套箭头函数会逐层向上查找词法环境,与普通函数一致
-
return () => x和return function() { return x }捕获的x完全相同 - 若外层是
function,箭头函数捕获的是该函数的活动对象(AO)或词法环境记录
function createCounter() {
let count = 0;
return {
inc: () => ++count,
get: () => count,
reset: function() { count = 0; }
};
}
const c = createCounter();
c.inc(); // 1
c.inc(); // 2
// 此时 count 仍被三个方法共同引用,无法 GC
最易被忽略的一点:闭包本身不“保存变量值”,它保存的是对变量绑定(binding)的引用。如果外层变量被重新赋值,所有闭包看到的都是最新值——这不是“缓存”,而是实时访问。











