闭包是javaScript作用域规则自然运行的结果,函数在定义时的词法作用域外被调用且仍访问该作用域变量即形成闭包;var循环中定时器输出相同值因共享同一变量绑定,let可解决;可通过chrome Scope面板或console.dir查看[[Scopes]]确认闭包。

闭包不是语法糖,也不是高级技巧——它是 javascript 作用域规则自然运行的结果。只要函数在定义时所处的词法作用域之外被调用,且仍能访问该作用域中的变量,闭包就已形成。
为什么 var 声明的循环变量在定时器里总输出最后一个值
这是闭包最典型的“踩坑现场”。根本原因在于:所有迭代中创建的函数共享同一个 var 变量绑定,而循环结束时该变量值已固定。
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // 输出:3, 3, 3
-
var是函数作用域,整个for循环只声明一个i - 三个
setTimeout回调都闭包了外层函数作用域中的那个i - 循环结束时
i === 3,所以全部打印3 - 改用
let即可解决:每次迭代都会为i创建独立的绑定(块级作用域)
如何确认某个函数是否形成了闭包
看它是否「记住并持续访问」了定义时所在词法环境中的变量,哪怕该环境本应已销毁。chrome devtools 的 Scope 面板是最直接的验证方式。
- 在函数内部打断点,打开
Scope面板,若出现Closure条目,且列出外部变量(如count,config),即存在闭包 - 闭包变量不会被垃圾回收,直到闭包函数本身不可达
-
console.dir(fn)在控制台展开函数对象,也能看到[[Scopes]]内含Closure
闭包常被误用的三种场景
闭包本身无害,但滥用会引发内存泄漏或逻辑错乱。
立即学习“Java免费学习笔记(深入)”;
- 在事件监听器中反复创建闭包并忘记
removeEventListener,导致 dom 节点和闭包变量长期驻留 - 把大型对象(如整个
document或组件实例)无意捕获进闭包,阻碍 GC - 在构造函数中为每个实例方法都闭包私有数据——不如用
#privateField或WeakMap更清晰可控
真正难的不是写出闭包,而是判断「这个变量是否必须被闭包住」。多数时候,显式传参比隐式捕获更易追踪、更少副作用。