重识闭包

重识闭包

三月 08, 2021 本文共计: 561 字 预计阅读时长: 2分钟

前言

为什么再次选择重温闭包这个神奇的物种呢?可能是脑海里还存在以前的疑虑吧?

函数就是闭包,这是最暴力的解释,也是最直观的解释,因为在全局作用域下,函数引用了函数作用域外的变量,像DOM引用;

函数内的函数引用了函数内部的变量或者函数外部的变量,闭包由此生成;(也是目前我能接受的理解,包含《你不知道的JavaScript中》所解释的,回调皆闭包);

切题引入

完成一个for循环,依次打印1-10,要求每隔一秒打印;

方案一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 记录setTimeout堆栈
let timerQueue = {}
// 闭包打印
function printf (i) {
// 形成闭包作用域
return () => {
// 每次打印结束后,清空上一次定时器的闭包引用
timerQueue[i] = null;
return console.log(i);
}
}

for (var i =0; i < 10 ; i ++) {
// 保存定时器引用
timerQueue[i] = setTimeout(printf(i), 1000*i);
}

方案二

1
2
3
4
5
for (let i = 0; i<10; i ++) {
setTimeout(() =>{
console.log(i)
}, 1000)
}

方案三

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// 睡眠函数
const sleep = (timer) => new Promise(resolve => {setTimeout(resolve, timer)});

// 打印
const print = async () => {
for (var i =0; i<10;i++) {
// 睡眠
await sleep(1000);
console.log(i);
}
}

print()

方案一主要利用了闭包的特性;方案二主要利用了ES6``let模块作用域的特性,for循环每次都保留上一次的值开始循环;方案三主要利用了async异步同步化;

函数的生命周期

alt

图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 记录setTimeout堆栈
let timerQueue = {}
// 闭包打印
function printf (i) {
// 形成闭包作用域
return () => {
// 每次打印结束后,清空上一次定时器的闭包引用
timerQueue[i] = null;
return console.log(i);
}
}

for (var i =0; i < 10 ; i ++) {
// 保存定时器引用
timerQueue[i] = setTimeout(printf(i), 1000*i);
}

alt

每次执行完一次for循环之后,蓝色的线条都会断开,作为函数生命周期的结束标志,但是内部函数还是会保留上次AO对象,且闭包作用域是内部的无名函数。

利用外围timerQueue对象保存定时器引用,适时清空,优化内存占用。