为什么需要它
闭包是回调、事件处理、模块封装、柯里化和 React Hooks 的底层基础。不理解它,就容易把“记住变量”误解成“复制值”,在循环、回调和共享状态里写出意外结果。
最小例子
js作用域在定义时确定
JavaScript 使用词法作用域:一个函数能访问哪些变量,由它“写在哪里”决定,而不是由它“在哪里被调用”决定。
下面的 show 定义在全局,所以它找的是全局的 x;run 里临时声明的 x 不会影响 show。
定义位置决定作用域
js闭包:函数 + 它记住的环境
内层函数引用外层函数的变量,并且外层函数返回后内层函数仍然存在——这时外层变量的环境不会立刻销毁,而是被内层函数保留下来,这就是闭包。
makeCounter 每次调用都会创建一个新的 count 绑定;返回的函数一直引用这个绑定,所以能持续累加。
- 闭包不是语法关键字,而是一种运行时现象
- 闭包保留的是变量绑定,不是调用瞬间的值快照
- 每次调用 makeCounter,都会产生一套独立的 count
每次调用都是独立环境
c1 和 c2 来自两次 makeCounter 调用,它们各自拥有独立的 count。因此 c1 累加不会影响 c2。
这也是“闭包状态”的常见用法:用函数调用创建私有状态,而不是把状态挂到全局。
两个计数器互不影响
js捕获的是绑定,不是快照
多个闭包引用同一个外层变量时,它们看到的是同一个绑定:一个函数改,另一个函数也能看到变化。
这也是经典循环题的原因:var 只有一个循环变量绑定,所有回调共享它;let 会为每次循环迭代创建新的绑定。
var 共享绑定,let 每次迭代独立
js用途与边界:别把“闭包”当成“泄漏”
闭包常用于模块私有状态、事件回调、柯里化、缓存和装饰器。只要还有函数引用着外部变量,这些变量就会继续存活。
这本身不是内存泄漏;只有当不再需要的闭包仍然被长期引用(例如全局数组、未移除的监听器)时,才会阻止回收。必要时主动解除引用。
还要注意:普通函数的 this 不是词法作用域,箭头函数捕获的是外层 this——这和闭包捕获变量是两套机制。
- 闭包让状态私有化,但也让变量的生命周期变长
- 需要长期保留时是特性,不再需要时是负担
- this 不是闭包变量;箭头函数和普通函数的 this 规则不同