闭包是JavaScript中内部函数持续访问其定义时外部作用域变量的能力;需同时满足嵌套定义、被传出并在外部保留引用、实际使用外部局部变量三个条件;常用于实现私有变量和模块封装,但需警惕内存泄漏与性能问题。

闭包 是 JavaScript 中一个函数“记住”并持续访问其定义时所在外部作用域变量的能力——哪怕那个外部函数早已执行完毕、退出调用栈。
它不是语法特性,而是词法作用域 + 函数作为一等公民 + 垃圾回收机制共同作用下的自然结果。
怎么一眼识别闭包?看三个硬条件
只要同时满足以下三点,就是闭包:
- 有嵌套:内部函数定义在外部函数体内
- 被传出:内部函数被
return、赋值给变量、传给定时器或事件监听器等,在外部保留引用 - 真用了:内部函数体里确实读取或修改了外部函数的局部变量(不是参数,也不是全局变量)
缺一不可。比如只定义没返回,或返回了但没访问外部变量,都不算闭包。
立即学习“Java免费学习笔记(深入)”;
为什么闭包常被用来做私有变量?
因为 JS 没有 private 关键字,而闭包能天然隔离变量生命周期:
function createBankAccount(initial) {
let balance = initial; // 外部无法直接访问
return {
deposit: (n) => { balance += n; },
withdraw: (n) => { if (n <= balance) balance -= n; },
getBalance: () => balance
};
}
const acc = createBankAccount(100);
acc.withdraw(30);
console.log(acc.getBalance()); // 70 —— balance 始终被锁在闭包里每个 createBankAccount() 调用都生成独立的 balance,互不干扰。这就是模块封装的底层逻辑。
闭包最常踩的坑:循环中绑定事件或定时器
典型现象:for (var i = 0; i console.log(i), 100); } 输出全是 3。
原因:var 声明变量提升且函数作用域共享,三次循环共用一个 i;闭包捕获的是变量本身,不是某次循环的快照。
解法只有两个:
- 改用
let:块级作用域让每次循环拥有独立i - 立即执行函数包裹:
(function(i) { setTimeout(() => console.log(i), 100); })(i)
现代代码优先选 let,但得清楚它解决的是“绑定时机”,不是闭包本身失效。
闭包不是银弹:内存与性能要心里有数
被闭包引用的变量不会被垃圾回收,哪怕你只留了一个 setTimeout 回调,它背后可能拖着整个父作用域的变量对象。
容易出问题的场景:
- 长生命周期对象(如单页应用中的全局控制器)反复创建闭包,又没及时解除引用
- 在 DOM 元素上绑定回调后忘了
removeEventListener,闭包连带持有 DOM 引用,导致内存泄漏 - 深层嵌套闭包 + 大量缓存数据,作用域链变长,变量查找变慢
判断是否过度使用闭包,就问一句:这个变量真的需要跨函数生命周期存在吗?还是只是图方便没传参?











