闭包是函数与其定义时词法环境的绑定关系,关键在于内部引用外层变量且在外部调用时仍可访问。常见误用是for循环中var声明导致回调共享同一i值;修复可用let、IIFE或setTimeout传参。实际用于模块封装、私有状态(如计数器、自定义Hook、配置工厂);但需警惕内存泄漏与调试困难。
JavaScript 中的闭包不是某种特殊函数,而是函数与其**定义时所在作用域中变量的绑定关系**。只要一个函数在定义后被传递到其他作用域(比如作为返回值、回调、事件处理器)并仍能访问其外层变量,它就形成了闭包。
关键判断依据:function 内部引用了外层函数的 var / let / const 变量,且该函数在外部被调用 —— 这时候那些变量不会被回收,就构成了闭包。
这是初学者踩得最多的一个坑:用 var 声明循环变量,又在异步回调里直接用 i,结果所有回调都输出最终的 i 值(比如 10)。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 输出 3, 3, 3
}
修复方式有三种:
立即学习“Java免费学习笔记(深入)”;
let 替代 var(块级作用域,每次迭代生成独立绑定)i 作为参数传入setTimeout 的第三个参数传参,再在回调里接收闭包最核心的价值是「封装」—— 不暴露变量,只暴露可控接口。典型场景包括:
createCounter() 返回带 increment 和 value 的对象,内部 count 不可直接访问useToggle)本质就是闭包,保存 state 并提供切换函数createApi(baseUrl) 返回一组已预填 baseUrl 的请求函数,避免每个调用都重复写地址function createLogger(prefix) {
return function(msg) {
console.log(`[${prefix}] ${msg}`);
};
}
const info = createLogger('INFO');
info('user logged in'); // [INFO] user logged in
闭包会阻止外层变量被垃圾回收,如果闭包长期存活(比如绑定到全局对象、DOM 事件、定时器),而它捕获的变量又很大(如缓存整个 DOM 树、大数组),就会造成内存泄漏。
调试时也容易困惑:Chrome DevTools 的 Scope 面板里能看到 Closure,但变量名可能被压缩或显示为 [[Scopes]] 下的匿名引用,不容易追溯来源。
所以实际开发中,要问自己两个问题:
WeakMap)?removeEventListener)?闭包本身不难理解,难的是判断“什么时候该用”和“什么时候不该留”。