闭包是JavaScript中函数记住并访问其定义时词法作用域的能力。它需满足三条件:外层函数含局部变量、内部定义函数、外层返回该函数;用于封装私有状态、保存上下文、函数工厂、缓存结果;常见坑是循环变量共享与内存泄漏。

闭包 是 JavaScript 中一个函数“记住并持续访问其定义时所在词法作用域”的能力——哪怕这个函数在别的地方被调用,它依然能读写外层函数的变量。
它不是语法糖,也不是高级技巧,而是你每天都在用却可能没意识到的底层机制:比如 setTimeout 回调里能访问 for 循环变量、React 的 useState 能保持状态、防抖函数 能记住上一次定时器 ID……全靠闭包撑着。
怎么一眼认出闭包?看这三步
只要同时满足以下三点,就是闭包:
- 有一个外层函数(含局部变量)
- 内部定义了一个函数(不一定要命名)
- 外层函数返回了这个内部函数(或以其他方式让内部函数逃逸出作用域)
典型信号:return function() { ... } 或 addEventListener('click', function() { ... }) —— 只要函数里用了外层的 let/const 变量,且该函数后续还会执行,基本就是闭包。
立即学习“Java免费学习笔记(深入)”;
为什么用闭包?不是为了炫技,而是解决这几个硬需求
闭包不是可选项,是某些场景下唯一干净的解法:
-
封装私有状态:比如计数器里的
count,外部无法直接改,只能通过increment()操作 -
保存上下文环境:事件监听中,每个按钮回调需要记住自己的
id或配置,不用闭包就得靠data-属性或全局映射表 -
实现函数工厂:比如生成不同超时时间的
debounce函数,debounce(fn, 300)和debounce(fn, 1000)各自维护独立的timerId -
缓存计算结果(memoization):
factorial(10)算过一次,下次直接返回,靠闭包把cache锁在函数内部
闭包最常踩的坑:内存泄漏和变量共享
闭包本身无害,但用错就容易翻车:
-
循环中创建闭包,所有回调共用同一个变量:用
var声明循环变量时,for (var i = 0; i console.log(i), 0)全输出3;改用let或包裹 IIFE 才能隔离 -
无意长期持有大对象引用:比如在闭包里存了整个
DOM节点或JSON数据,又没清理,GC 就收不走 -
误以为闭包能“冻结”值:闭包捕获的是变量的引用,不是快照。如果外层变量被改了(比如
obj.name = 'new'),闭包里看到的也是新值
什么时候该主动用闭包?看这几个信号
遇到以下情况,别绕弯,直接上闭包:
- 你需要一个“带记忆”的函数,比如
createLogger(prefix)返回带前缀的日志函数 - 你在写工具函数,希望用户传入配置后,得到一个预设好行为的函数(如
createUrlBuilder(baseUrl)) - 你要给多个元素绑定事件,又不想污染全局或重复查 DOM
- 你发现正在反复写
const config = {...}; return () => {...}这种模式 —— 那就是在手写闭包
真正难的不是写闭包,而是判断「该不该用」以及「用完要不要释放」。很多 bug 不是闭包本身的问题,是忘了它会让变量活得比预期久一点。











