网站
引入
闭包用来解决一个问题:函数执行完之后,有些变量还想继续被后面的代码使用。
简单理解,闭包就是函数记住了它创建时所在作用域里的变量。
正文
定义
定义
闭包是指一个函数可以访问并记住它外层函数作用域中的变量,即使外层函数已经执行结束。
闭包通常出现在:函数里面返回另一个函数。
function outer() {
const message = "你好";
function inner() {
console.log(message);
}
return inner;
}
const fn = outer();
fn();这里 outer() 执行完之后,message 按理说应该结束了。
但是 inner() 还在使用 message,所以 message 被保留下来了。
这就是闭包。
语法
闭包最常见的结构是:外层函数里定义变量,再返回一个内部函数。
function outer() {
let count = 0;
return function () {
count = count + 1;
console.log(count);
};
}
const add = outer();
add();
add();
add();输出结果:
1
2
3count 没有随着 outer() 执行结束而消失,因为返回出去的函数还在使用它。
为什么需要闭包
闭包常用来保存一些不想直接暴露到外部的数据。
function createCounter() {
let count = 0;
return function () {
count = count + 1;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());这里的 count 只能通过 counter() 修改,外部不能直接改它。
这样可以让数据更安全,不容易被随便修改。
常见用法
| 用法 | 作用 |
|---|---|
| 保存变量状态 | 让变量在函数执行结束后继续存在 |
| 封装私有数据 | 外部不能直接访问内部变量 |
| 创建计数器 | 每次调用时保留上一次的结果 |
| 配合回调函数 | 回调执行时还能访问外层变量 |
| 函数工厂 | 根据不同参数生成不同函数 |
例子
创建计数器
这个例子中,count 会一直被内部函数记住。
function createCounter() {
let count = 0;
return function () {
count = count + 1;
console.log(count);
};
}
const counter = createCounter();
counter();
counter();
counter();count 是 createCounter() 里的局部变量,外部不能直接访问。
但是返回出来的函数可以继续使用它。
保存不同的配置
这个例子中,外层函数接收 name,内部函数会记住这个 name。
function createGreeter(name) {
return function () {
console.log("你好," + name);
};
}
const greetTom = createGreeter("Tom");
const greetJack = createGreeter("Jack");
greetTom();
greetJack();greetTom() 记住的是 Tom。
greetJack() 记住的是 Jack。
它们互不影响。
配合事件使用
闭包也经常出现在事件回调里。
<button id="btn">点击</button>function bindClick() {
let count = 0;
const btn = document.querySelector("#btn");
btn.addEventListener("click", function () {
count = count + 1;
console.log("点击次数:" + count);
});
}
bindClick();bindClick() 执行结束后,点击事件里的函数还能继续使用 count。
这也是闭包。
特点
- 闭包可以让函数记住外层作用域里的变量
- 外层函数执行结束后,被内部函数使用的变量不会马上销毁
- 闭包常见写法是函数里返回函数
- 闭包可以用来保存状态
- 闭包可以用来封装私有变量
- 闭包用多了可能导致变量一直被占用,不需要时要注意释放引用
常见错误
以为局部变量一定会消失
普通情况下,函数执行完,局部变量就不能再访问。
function test() {
const name = "Tom";
}
test();
console.log(name);上面这样访问不到 name。
但如果内部函数还在使用这个变量,它就可能被保留下来。
function test() {
const name = "Tom";
return function () {
console.log(name);
};
}
const fn = test();
fn();循环中使用闭包时结果不符合预期
使用 var 时,循环里的函数可能会共享同一个变量。
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}输出结果可能是:
3
3
3因为 var 没有块级作用域,三个回调函数共享同一个 i。
更推荐使用 let。
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}输出结果:
0
1
2let 每次循环都会形成新的块级作用域,所以每个函数记住的是不同的 i。
理解
可以把闭包理解为:函数带着一个小背包。
这个背包里装着它创建时能访问到的变量。
即使外层函数已经执行完了,只要内部函数还在,这些被用到的变量就还会被记住。
闭包不是一个新的语法,而是一种函数和作用域结合后产生的现象。
重点不是死记定义,而是看懂:哪个函数记住了哪个变量。
引出
理解闭包之后,可以继续学习 JS 作用域,知道变量到底在哪些位置可以被访问。