原文链接: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 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在任务队列尾部,肯定是最后打印的。所以输出顺序就完了。。
分析下来其实很简单,但是在我不知道这些细节之前,这个题完全是知其然不知其所以然。。现在看一下,其实很简单,不要慌,慢慢分析就行了。