闭包是词法作用域、函数作为一等公民和垃圾回收机制共同作用的自然结果;当内部函数引用外部变量并逃出定义作用域执行时形成闭包,否则不构成;常见问题包括循环变量捕获异常和内存泄漏。

闭包不是 JavaScript 故意“加”的一个功能,而是词法作用域 + 函数作为一等公民 + 垃圾回收机制三者自然碰撞出的结果——它不靠语法关键字声明,而是在你写嵌套函数并返回/传递时,自动产生。
为什么 function 返回另一个 function 就会形成闭包?
- JavaScript 在函数定义时就固定了它的词法作用域(即“它写在哪,就认哪的变量”),而不是调用时才查;
- 当内部函数被返回或赋值给外部变量(比如
const counter = createCounter()),它仍带着对原始定义环境的引用; - 引擎发现这个内部函数还在用
count,即使createCounter已执行完、本该销毁局部变量,count也不能被回收——否则counter()下次调用就会报错或读到undefined。
这背后没有魔法,只是引擎按规则走:只要变量还在被活引用,就得留着。
常见误判:不是所有嵌套函数都是闭包
-
inner定义在outer里 ≠ 形成闭包; - 关键看它是否 真正引用了外部变量,且该函数 逃出了定义它的作用域去执行。
function outer() {
const x = 1;
function inner() {
console.log('没用x,不算闭包'); // 没引用外部变量
}
return inner;
}上面的 inner 不是闭包。而下面这个是:
function outer() {
const x = 1;
return function() { console.log(x); }; // 引用了 x,且被返回出去
}闭包最常踩的坑:内存泄漏和循环中变量捕获异常
-
在
for循环里直接给元素绑事件,用var声明的循环变量会被所有闭包共享:立即学习“Java免费学习笔记(深入)”;
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 全部输出 3 }解决方式:
- 改用
let(块级作用域,每次迭代新建绑定); - 或用立即执行函数包裹:
(function(i) { setTimeout(() => console.log(i), 100); })(i); - 或用
forEach替代for。
- 改用
长期持有 DOM 引用的闭包(比如事件监听器里存了整个节点),又没手动
removeEventListener,变量就一直无法释放。
闭包本身无害,问题出在「引用没及时断」——它像一根看不见的线,把本该消失的变量拽在内存里。写的时候多问一句:这个变量,我之后真还需要吗?











