闭包是函数与其词法环境的组合,需同时满足:存在引用外部局部变量的内部函数、该函数被返回或脱离原始上下文;var/let在循环闭包中因作用域机制不同导致输出差异;闭包会阻止捕获变量被垃圾回收,易引发内存泄漏。

闭包不是某种特殊函数,而是函数与其词法环境的组合——只要一个函数在定义时能访问其外层作用域中的变量,并且这个函数在定义之外被调用,就形成了闭包。
闭包产生的核心条件
必须同时满足三个条件:
- 存在一个内部函数(
function或箭头函数) - 该内部函数引用了外部函数的局部变量(非全局、非参数传入)
- 外部函数返回了这个内部函数(或以其他方式使它脱离原始执行上下文)
缺一不可。只定义不返回,或返回但没保存/调用,都不构成实际起效的闭包。
var 与 let 在闭包中的行为差异
循环中创建多个闭包时,变量声明方式直接影响结果:
立即学习“Java免费学习笔记(深入)”;
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100); // 输出 0, 1, 2
}
原因:var 声明的 i 是函数作用域共享的,所有定时器回调共用同一个 i;而 let 每次迭代都绑定独立的绑定记录(binding),每个闭包捕获的是各自迭代中的 j 值。
闭包导致的内存保留问题
闭包会阻止其词法环境中变量被垃圾回收,即使外部函数早已执行完毕:
- 如果闭包长期存活(如绑定到全局对象、事件监听器、定时器),它所捕获的变量也会一直驻留内存
- 特别危险的是捕获了大对象(如
document、大型数组、DOM 节点集合) - 调试时可通过 Chrome DevTools 的
Memory面板 +Heap Snapshot查看“Closure”条目下的 retained size
常见误用:addEventListener 中直接写匿名闭包却不提供 removeEventListener 清理路径,导致重复绑定+内存泄漏。
闭包本身没有“副作用”,但它的生命周期和捕获逻辑很容易被忽略——真正难的不是写出闭包,而是判断某个变量是否被意外长期持有。










