前言
在 JavaScript 中,闭包是一个非常经典、也非常高频的知识点。
很多人在刚接触闭包时,会觉得这个概念有点抽象,但实际上,只要结合代码去理解,闭包并没有那么难。
闭包在实际开发中非常常见,比如:
- 计数器
- 函数工厂
- 私有变量
- 回调函数
- 模块化封装
本文就来系统讲清楚:什么是闭包、闭包有什么作用、实际开发中怎么用。
一、什么是闭包?
闭包可以简单理解为:
函数 + 它定义时所处的作用域环境
换句话说:
一个函数即使在外部执行,仍然可以访问它定义时所在作用域中的变量,这种现象就和闭包有关。
来看一个经典例子:
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3
这里的
inner
就形成了闭包。
原因是:
-
outer()执行结束后,本来它内部变量
count应该被销毁
-
但是
inner还在使用
count -
所以
count会继续保存在内存中
二、闭包的核心特点
1)可以访问外部函数的变量
function outer() {
let name = 'Tom';
function inner() {
console.log(name);
}
return inner;
}
const fn = outer();
fn(); // Tom
2)可以让变量长期保存
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
这里
count
不会因为
createCounter()
执行结束就消失,而是会一直保留。
三、为什么需要闭包?
闭包主要有几个作用:
1)保存状态
最典型的就是计数器。
2)实现私有变量
让某些数据不能被外部直接修改。
3)避免全局变量污染
把变量封装在函数内部。
4)在异步回调中保留上下文数据
例如定时器、事件回调中保存当前值。
四、闭包的实际应用
场景 1:计数器
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
场景 2:私有变量
function createUser(name) {
let password = '123456';
return {
getName() {
return name;
},
checkPassword(value) {
return value === password;
}
};
}
const user = createUser('Alice');
console.log(user.getName()); // Alice
console.log(user.checkPassword('123456')); // true
console.log(user.password); // undefined
这里
password
外部无法直接访问。
五、经典面试题:循环中的闭包
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
输出结果:
3
3
3
原因是:
-
var没有块级作用域
-
循环结束后
i变成了
3 -
定时器执行时,拿到的都是同一个
i
解决方式 1:使用let
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
输出:
0
1
2
解决方式 2:使用闭包
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => {
console.log(j);
}, 1000);
})(i);
}
六、闭包的注意点
闭包虽然强大,但也不能滥用。
注意点:
- 闭包会让变量继续存活
- 如果保存了大量不再需要的数据,可能增加内存占用
- 逻辑复杂时会增加理解成本
所以闭包要合理使用,不要为了“炫技”而使用。
七、总结
闭包的本质并不神秘,可以简单记住一句话:
闭包就是函数可以“记住”并访问它定义时所在作用域中的变量。
它最常见的用途包括:
- 保存状态
- 私有变量
- 回调场景
- 模块化封装