# 我对闭包的最初认知
- 在
函数foo
中返回函数bar
,此时就产生了闭包。
function foo() {
let a = 1;
function bar() {
console.log(a);
}
// 产生闭包
return bar;
}
const baz = foo();
baz(); // 1 ---> 这就是闭包的效果
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 闭包就是是:假如一个函数能访问外部的变量,那么就形成了一个闭包,不是一定要返回一个函数。
/** 举个🌰 */
let a = "hello qile";
/**产生闭包 */
function foo() {
console.log(a);
}
1
2
3
4
5
6
2
3
4
5
6
![]
# 闭包的作用
闭包使得函数可以继续访问定义是的词法作用域
# 闭包的实质
当函数可以记住并访问所在词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
用上面的例子来解释就是,函数bar
是在函数foo
中定义的,但函数bar
确在定义时的词法作用域以外的地方被调用了。
最后它调用之后可以打印出foo
中的变量a
。
# 循环和闭包
for (var i = 1; i <= 5; i += 1) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
1
2
3
4
5
2
3
4
5
从上面的代码行为上预期是分别输出数字 1 ~ 5 ,每秒一次,每次一个。但实际上可能不是这样
从图片上可以看到,上面的代码在运行是会以每秒一次的频率输出五次 6。这个 6 是循环终止条件首次命中时 i 的值。
因为定时器中的回调函数是在循环结束后才会被执行,因为同步代码执行完之后才会去执行微任务和宏任务。
那么这个缺陷的原因是啥呢?其实就是我们想在每次迭代的时候去捕获一个 i 当时的副本,但是根据作用域的工作原理,实际情况是尽管
循环中的 5 个定时回调函数是分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此它们打印的 i 就是最后一次循环条件被终止时的 i。
# 如何解决上面代码的缺陷
- 给每个回调函数都产生一个闭包作用域
for (var i = 1; i <= 5; i += 1) {
// 使用 IIFE (立即调用函数)为每一个迭代都产生一个新的作用域。使得延迟函数的回调
// 可以将新的作用域封闭在每个迭代的内部,每个迭代中都会含有一个具有正确值的 i 供访问。
(function (j) {
setTimeout(() => {
console.log(j);
}, j * 1000);
})(i);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 使用块作用域
// let声明,可以用来劫持块作用域,并且在这个块作用域中声明一个变量。
// for 循环头部使用 let 声明会产生一种特殊行为,会让 i 在每次迭代时都会重新声明
// 而且之后的每个迭代都会用上一次迭代结束时的值来初始化 i 这个变量
for (let i = 1; i <= 5; i += 1) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8