定义
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
简单总结:
- 一个函数调用时能够访问到外部函数定义的变量或函数时,就会形成闭包。
- 闭包的本质是一个对象,存储着函数所用到的外部函数定义的变量或函数。
通过几个例子来帮助理解:
例子一(通过parent函数中调用child函数来创建闭包):
1: function parent() {
2: let parentVal = 0;
3:
4: function child() {
5: console.log(parentVal);
6: }
7: child();
8: }
9: parent();
- 将断点打在第5行后执行代码。
- 代码的执行过程中会创建parent的执行上下文,执行child时,会创建child的执行上下文,child函数可以使用到parent和全局定义的变量。
- 由于child函数使用到parent中定义的parentVal,此时child函数执行时就创建了一个Closure,Closure中存放着child使用到外部函数定义的变量。
- 小结:
- parent函数中调用child函数,且child函数使用到parent中定义的parentVal,由此在child调用时产生闭包。
- 由于我们无法直接调用child,只能通过调用parent来调用child,因此每次都会创建一个parent的执行上下文,新的parentVal变量(值为0),调用child时会创建child的执行上下文,此时child访问到的parentVal始终是初始值0。此时闭包对我们来说没有太多的应用价值。
例子二(通过parent函数中返回child函数来创建闭包):
1: function parent() {
2: let parentVal = 0;
3: return function child() {
4: console.log(parentVal);
5: parentVal++; // 注意例子二,在child中多加了parentVal++的操作
6: }
7: }
8:
9: const demo = parent();
10: demo();
11: demo();
- 将断点打在第10、11行后执行代码。
- 第9行代码执行后,返回child函数,赋值给demo变量(此时demo是一个具有闭包且功能与child一样的函数)。
- 此时parentVal的值为0,执行第10行代码(即执行demo)后,观察第一次的parentVal的变化。
- 继续执行demo,观察第二次parentVal的变化。
- 小结:
- 通过parent函数返回child的函数来创建闭包。此时我们会发现在调用parent函数后,demo函数即实现了child的功能又创建了一个闭包。
- 这个闭包中存储的是child用到的且属于parent定义的parentVal,是一个有“记忆”功能的状态值(每次执行demo函数对parentVal进行自增操作后,parentVal都会保留执行后的值)。
- 为什么闭包中parentVal会具有“记忆”功能呢?因为demo变量赋值parent调用后返回的child函数后,就已经创建好了闭包。且在demo调用时,不再需要创建parent的执行上下文,而是直接从闭包中获取parentVal。
闭包的应用
应用一:防抖节流
function debounce(fn, delay) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
let curTime = +new Date();
if (curTime - lastTime > delay) {
fn.apply(this, args);
lastTime = curTime;
}
};
}
应用二:错误请求重新发送
function getData(params, count = 0) {
return new Promise((resolve, reject) => {
// api
getList(params).then(res => {
resolve(res);
}).catch(e => {
count++;
console.log(`累积请求失败${count}次`);
if (count >= 2) {
reject()
} else {
getData(params, count);
}
})
})
}
function getList() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("请求失败");
}, 1000);
})
}
getData({})
- 如果存在表述有误或理解偏差的地方,欢迎评论指正~