闭包是函数与其定义时词法作用域绑定形成的运行时结构;识别关键:外层函数返回内层函数,且内层函数引用外层局部变量,如createCounter中count被持续访问。

JavaScript 闭包不是“某个特殊函数”,而是一个函数与其词法作用域绑定后形成的**运行时结构**——只要一个函数能访问并持有它定义时所在作用域里的变量,哪怕外层函数早已执行完毕,这个函数就是闭包。
怎么一眼识别闭包?看这三行关键代码
闭包的最小可验证形态非常朴素:外层函数返回内层函数,且内层函数引用了外层的局部变量。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter(); // ← 这里就产生了闭包
console.log(counter()); // 1
console.log(counter()); // 2
关键点:
-
count是createCounter内部的局部变量,按理说函数执行完就该被回收 - 但
return出去的匿名函数仍能读写count,说明 JS 引擎把它“锁”在了闭包中 - 每次调用
counter(),操作的都是同一份count——这就是状态保持
闭包最常用却最容易写错的场景:事件监听器中的循环变量
这是新手高频翻车现场:用 for 循环给多个按钮绑点击事件,结果所有回调都输出最后一个索引值。
立即学习“Java免费学习笔记(深入)”;
for (var i = 0; i < 3; i++) {
btns[i].addEventListener('click', function() {
console.log(i); // 全是 3!因为 var 是函数作用域,i 最终为 3
});
}
修复方式有三种,本质都是靠闭包“捕获当前轮次的值”:
- 改用
let(推荐):let是块级作用域,每轮循环生成独立绑定 →console.log(i)输出 0、1、2 - 用 IIFE(立即执行函数)包一层:
(function(j) { ... })(i),把i当参数传进去形成闭包 - 用
forEach替代for:arr.forEach((_, i) => { ... }),回调函数天然形成闭包
闭包在真实项目里干这些活,不是炫技
它解决的是“如何安全地携带上下文”这个根本问题:
-
私有状态封装:比如
createSafe()返回的deposit和checkBalance方法共享一个无法从外部篡改的money变量 -
函数工厂:如
memoizeFunction(add)返回的缓存版add,内部闭包持有一个cache对象 -
防抖/节流函数:
debounce(fn, delay)返回的函数必须记住上次定时器 ID 和原始fn,否则无法清除或延迟执行 -
AJAX 请求配置复用:比如
createApi(baseUrl)返回一堆get/post方法,它们都闭包着同一个baseUrl和默认 headers
闭包本身没有性能问题,但滥用会导致内存泄漏:比如给大量 DOM 元素绑定闭包回调,又没手动解绑,那些被闭包引用的变量就一直活在内存里。真正要警惕的,从来不是“用了闭包”,而是“忘了清理闭包持有的大对象或 DOM 引用”。











