JavaScript 闭包到底是什么:用计数器例子一次讲清
学 JavaScript 时,「闭包」大概是被问得最多、也最容易越听越糊涂的概念之一。其实它没有那么玄:一个函数记住了它出生时所在作用域里的变量,哪怕它后来在别的地方执行,这些变量依然能访问——这就是闭包。
先看一个计数器
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
按直觉,createCounter 执行完之后,局部变量 count 应该随着函数结束被回收。可结果是它不但活着,还能被返回的内部函数持续修改。原因就是内部函数在定义时「带走」了 count 所在的词法作用域——这个被带走的变量集合,就是闭包的核心。
三个关键点
第一,闭包是定义时决定的,不是调用时。 函数能访问哪些变量,看它写在哪里,不看它在哪里被调用。把上面返回的函数传到任何地方执行,它认的还是当初那个 count。
第二,每次调用外层函数,都会产生一份独立的闭包。
const a = createCounter();
const b = createCounter();
a(); a(); // a 的 count 到 2
console.log(b()); // 1,b 有自己独立的 count
a 和 b 各自持有一份互不干扰的 count,这也是闭包能用来做「私有状态」的原因:外面的代码碰不到 count,只能通过返回的函数去改它。
第三,循环里最容易踩坑。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出 3、3、3,而不是 0、1、2
var 没有块级作用域,三个回调闭包共享同一个 i,等定时器触发时循环早已结束、i 已经是 3。改成 let 后,每轮循环都是一个独立的绑定,输出就变回 0、1、2。这个例子能想通,闭包和作用域基本就过关了。
它有什么实际用处
- 封装私有变量:像计数器这样,只暴露方法、不暴露数据本身;
- 函数工厂:按参数批量生成行为略有差别的函数,比如不同折扣率的计算器;
- 回调与事件处理:回调里记住当时的上下文变量,是前端代码里最常见的闭包身影;
- 防抖、节流:计时器句柄就是靠闭包保存下来的状态。
一个提醒
闭包会让被引用的变量无法被垃圾回收。如果在闭包里囤积大对象、又长期不释放,就可能造成内存占用持续增长。用完的大数据该置空就置空,别让「记住」变成「包袱」。
一句话总结:闭包 = 函数 + 它定义时所在作用域的变量。把计数器这个例子亲手敲一遍、再改两下(比如让两个计数器互相干扰试试),比背十遍定义都管用。
- 点赞
- 收藏
- 关注作者
评论(0)