JS深入系列之闭包

JS深入系列之闭包

六月 01, 2021 本文共计: 802 字 预计阅读时长: 3分钟

今天是儿童节,然鹅,学习还是要的,但是还是要祝各位大朋友,儿童节快乐~~~


紧接着上一篇的内容,我们来回顾一遍JS闭包。

什么是闭包?

列举下我所看到的相对比较官方的解释:

  • 闭包是指那些能够访问自由变量的函数。— MDN
  • 从技术角度来讲,所有函数都是闭包。 — 《JavaScript权威指南》

那意味着:

1
2
3
4
const temp = 123;
function print() {
console.log(temp);
}

上述代码也相当于是闭包。

但是ECMAScript中的规范定义,闭包即便是上下文销毁,也依旧存在,且保持着对于自由变量的引用。

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
补充:这里的函数指的是闭包函数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function localScope() {
let a = 123;
function print() {
console.log(a++);
}
return print;
}

const add = localScope();

add()
add()
add()
add()
1
2
3
4
5
6
7
8
9
10
# print console

/Users/wangbinlin/.nvm/versions/node/v14.16.0/bin/node /Users/wangbinlin/Project/daily_exercise/blog/test.js
123
124
125
126

Process finished with exit code 0

现在,你可以清楚的看到闭包的魅力。

下面过两道面试必刷题:

题目来源于:https://github.com/mqyqingfeng/Blog/issues/9

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = [];

for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}

data[0]();
data[1]();
data[2]();

解析过程请参见:https://github.com/mqyqingfeng/Blog/issues/9

题目二

请写一个计时器,每隔一秒打印一次

这道题,虽然实现思路很多,其实就像考察闭包!下面我来列举下常规解法:

解法一

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

why?为什么这样写可以呢?我想市面上百分之50的回答应该都是:

因为let可以创建块作用域,完了结束。

其实我这里再出一段代码:

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

结果肯定不是预期,但是我也用了 let,请思考一分钟。

for循环头部的let声明有一个特殊的行为,在每次循环的时候不止被声明一次,每次迭代都会声明,且初始值用上次的,也就是会出现下面的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一次
for(let i = 0; i<60;i++) {
{
setTimeout(() => {
console.log(i);
}, 1000 * i);
}
}
// 第二次
for(let i = 1; i<60;i++) {
{
setTimeout(() => {
console.log(i);
}, 1000*i);
}
}

// 此处省略 58 次

现在能看懂吧,第二个写法虽然用了let,但是它不在for循环头部。

解法二

闭包现身

1
2
3
4
5
6
7
for (var i = 0; i < 60; i++) {
((i) => {
setTimeout(() => {
console.log(i)
}, i*1000);
})(i)
}
1
2
3
4
5
6
7
for (var i = 0; i < 60; i++) {
setTimeout(print(i), i*1000);
}

function print(i) {
return () => console.log(i)
}

上述两种写法,应该是比较常见的闭包写法,其实就是前文提到的自由变量闭包作用域

当函数可以记住并且访问所在词法作用域,即使函数是在当前词法作用域外执行的,这时候就产生了闭包。
—– 《你不知道的JavaScript》上卷 p57