最近在复习 JavaScript 基础时,重新看了一遍闭包。这个概念一开始会觉得有点抽象,但只要结合函数作用域和实际例子来看,其实并不难理解。
一、什么是闭包
简单来说,闭包就是:函数可以记住并访问它定义时所在的作用域,即使这个函数在外部被调用,依然可以使用原来的变量。
换句话说,函数执行结束后,正常情况下它内部的变量应该被释放;但如果有一个内部函数仍然引用这些变量,那么这些变量就会继续保留下来,这种现象就是闭包。
二、先看一个例子
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3
在这个例子里:
outer()执行时,创建了变量count。- 它返回了内部函数
inner()。 - 虽然
outer()已经执行完了,但是inner()仍然引用着count。 - 所以每次调用
fn(),都还能继续访问并修改这个变量。
这就是一个典型的闭包。
三、为什么闭包很有用
闭包的常见作用,主要有下面几种:
1. 封装私有变量
function createCounter() {
let num = 0;
return {
add() {
num++;
return num;
},
get() {
return num;
}
};
}
const counter = createCounter();
console.log(counter.add()); // 1
console.log(counter.add()); // 2
console.log(counter.get()); // 2
这里的 num 无法直接从外部修改,只能通过返回的方法访问。这样就起到了“私有变量”的效果。
2. 保存状态
很多时候,我们希望某些数据在多次调用之间能够保留下来,闭包就非常适合这种场景。
比如计数器、缓存、函数工厂,本质上都在利用闭包保存状态。
3. 生成不同配置的函数
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
这里 add5 和 add10 都是闭包,它们分别记住了不同的 x。
四、闭包使用时要注意什么
闭包很好用,但也不是越多越好。
1. 占用内存
因为闭包会让被引用的变量继续保存在内存中,如果使用不当,可能会造成不必要的内存占用。
所以当某些闭包不再需要时,应该尽量让相关引用释放掉。
2. 循环中的变量问题
在老项目里,经常会看到 var 和闭包一起使用时带来的问题:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
输出结果通常是:
3
3
3
原因是 var 没有块级作用域,三个回调共享的是同一个 i。
更推荐直接使用 let:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
这样输出就是:
0
1
2
五、怎么理解闭包
如果只记一句话,我觉得可以记这个:
闭包不是某一种特殊语法,而是函数和它所能访问的词法作用域共同形成的结果。
所以学习闭包,关键不是死记定义,而是理解两件事:
- 函数可以嵌套定义。
- 内部函数可以访问外部函数的变量。
当内部函数在外部继续被使用时,闭包自然就产生了。
六、参考资料
如果想继续深入,可以直接阅读 MDN 的说明,写得比较系统: