原文链接:https://zhuanlan.zhihu.com/p/25407758

看了下知乎上某位大佬分享的一个题,我自己分析一下:

1
2
3
4
5
6
7

for (var i = 0; i < 5; i++) {

console.log(i);

}

上面这行代码明显是直接打印出 0-4,以下为chrome控制台复制的打印结果:

1
2
3
4
5
6
7
8
9
10
11

column.raven.bcd2ad0d66eba703dd35.js:1 0

column.raven.bcd2ad0d66eba703dd35.js:1 1

column.raven.bcd2ad0d66eba703dd35.js:1 2

column.raven.bcd2ad0d66eba703dd35.js:1 3

column.raven.bcd2ad0d66eba703dd35.js:1 4

然后开始第一次进化:
1
2
3
4
5
6
7
8
9
10
11

for (var i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 1000 * i);

}

上面这段代码,分析一下:

1
2
3
4
5
6
7
8
9
10
11

i = 0 时: 延迟 0s

i = 1 时: 延迟 1s

i = 2 时: 延迟 2s

i = 3 时: 延迟 3s

i = 4 时: 延迟 4s

所以会立马打印一个值,然后没过一秒打印一个值,一共输出五个值。但是这个值是什么呢?因为for循环是立即执行的,而setTimeout哪怕是延迟0s,也是会被放到任务队列后面的,参照http://ruomuc.gitee.io/blog/2019/01/02/event%20loop/。所以第一次执行setTimeout的回调的时候,i的值就已经是5了。

那应该怎么改才能输出 0 到 4 呢?

一个很简单闭包就解决了,也是js的经典问题

1
2
3

或者不用闭包,用新语法let

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

for (var i = 0; i < 5; i++) {

(function(i) {

setTimeout(function() {

console.log(i);

}, i * 1000);

})(i);

}

或者不用闭包,用新语法let:

1
2
3
4
5
6
7
8
9
10
11

for (let i = 0; i < 5; i++) {

setTimeout(function() {

console.log(i);

}, 1000 * i);

}

关于let和var 的区别可以去看下阮一峰的ES6入门

以上代码输出结果都为:

1
2
3
4
5
6
7
8
9
10
11

column.raven.1e01f608522ae2f2785f.js:1 0

column.raven.1e01f608522ae2f2785f.js:1 1

column.raven.1e01f608522ae2f2785f.js:1 2

column.raven.1e01f608522ae2f2785f.js:1 3

column.raven.1e01f608522ae2f2785f.js:1 4

删掉这个 i 会发生什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

for (var i = 0; i < 5; i++) {

(function() {

setTimeout(function() {

console.log(i);

}, i * 1000);

})(i);

}

上述代码虽然写了闭包,但是没有对i保持引用,所以可定还是没有用闭包之前的结果。setTimeout内部的i的值引用的还是for循环的i的值。也就是5。

再次进化
1
2
3
4
5
6
7
8
9
10
11

for (var i = 0; i < 5; i++) {

setTimeout((function(i) {

console.log(i);

})(i), i * 1000);

}

setTimeout的第一个参数必须是需要编译的代码或者是一个函数方法,而如果直接传入一行可执行代码,那么抱歉,这里会立即执行,没有延迟效果。

所以上述代码会立即输出 0 -4。 ps:又回到最初的起点?

好了,面试官不耐烦了 ,放大招了~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

setTimeout(function() {

console.log(1)

}, 0);

new Promise(function executor(resolve) {

console.log(2);

for( var i=0 ; i<10000 ; i++ ) {

i == 9999 && resolve();

}

console.log(3);

}).then(function() {

console.log(4);

});

console.log(5);

上述代码的输出顺序是什么?

分析一波:

首先,setTimeout会把回调函数放到任务队列尾部,然后往下,new Promise这个函数会立即执行,会立马输出 2 和3, then里面的东西会被放到当前tick结束执行,那接下来肯定会打印 5,然后出去会打印4,现在看看剩下两个,setTimeout在任务队列尾部,肯定是最后打印的。所以输出顺序就完了。。

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

2

3

5

4

1



分析下来其实很简单,但是在我不知道这些细节之前,这个题完全是知其然不知其所以然。。现在看一下,其实很简单,不要慌,慢慢分析就行了。