1. 错误示例
1 2 3 4 5 6 7
| function print() { for (var i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, 1000); } }
|
上面的代码,我们期望每隔一秒,输出一个数,而且这个数是每次循环i的值。但是期望也只是期望,实际情况是一秒钟之后直接输出了10个10。那么问题就来了,我们怎么实现我们的期望值呢?
由此我们先来尝试几种解决方案,并作一一对比:
- 每隔一秒输出一个10
1 2 3 4 5 6 7 8
| function print () { for (var i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, i * 1000); } }
|
- 等待一秒之后接连输出0-9
1 2 3 4 5 6 7 8 9 10
| function print () { for (var i = 0; i < 10; i++) { (function (i) { setTimeout(() => { console.log(i); }, 1000); })(i); } }
|
综上所述,第一种方式实现了每隔一秒输出一个数值的功能,第二种方案实现了每次输出不同的i值的功能,如果将这两种方案综合起来,那就是我们想要的结果,即:
1 2 3 4 5 6 7 8 9 10
| function print () { for (var i = 0; i < 10; i++) { (function (i) { setTimeout(() => { console.log(i); }, i * 1000); })(i); } }
|
2. 问题及解决办法
之所以会出现这种情况差异,是因为 js 是单线程的,有一个事件队列机制,setTimeout 和 setInterval 的回调会到了延迟时间塞入事件队列中,排队执行。而在此同时,for循环并不会一直等待,而会直接循环完毕,所以等到 setTimeout 开始执行的时候,原先我们预想的i早就变成了10,所以就会出现之前我们遇到的问题。
为了实现我们想要的功能,就要解决两个问题:
- 怎么保存每次循环的临时变量 i
- 怎么让定时器产生时间间隔
弄懂了我们要解决的问题,那么接下来敲代码就行云流水了,关于第一个问题,我们需要保存每次循环 i 的值,关于第二个问题,我们只需要控制定时器的时间就好,以下即是几种解决方案:
3. 正确示例
1. 添加一个函数调用
1 2 3 4 5 6 7 8 9 10 11 12
| function print () { for (var i = 0; i < 10; i++) { timePrint(i); } }
function timePrint (i) { setTimeout(() => { console.log(i); }, i * 1000); }
|
2. 立即执行函数(闭包)
1 2 3 4 5 6 7 8 9
| function print () { for (var i = 0; i < 10; i++) { (function (j) { setTimeout(() => { console.log(j); }, i * 1000); })(i); } }
|
3. setTimeout另一种用法
1 2 3 4 5 6 7
| function print() { for (var i = 0; i < 10; i++) { setTimeout(function(j) { console.log(j); }, 1000 * i, i); } }
|
setTimeout()的返回值是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。需要注意的是setTimeout()和setInterval()共用一个编号池,技术上,clearTimeout()和 clearInterval() 可以互换。但是,为了避免混淆,不要混用取消定时函数。
4. let方法保存不同阶段的值
1 2 3 4 5 6 7
| function print() { for (let i = 0; i < 10; i++) { setTimeout(() => { console.log(i); }, i * 1000); } }
|
5. Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const tasks = [];
for (var i = 0; i < 10; i++) { (function(j) { tasks.push(new Promise(resolve => { setTimeout(() => { console.log(j); resolve(); }, 1000 * j); })) })(i); }
Promise.all(tasks);
|
6. 简洁版的Promise
1 2 3 4 5 6 7 8 9 10 11 12 13
| const tasks = []; const output = (i) => new Promise(resolve => { setTimeout(() => { console.log(i); resolve(); }, 1000 * i); })
for (var i = 0; i < 10; i++) { tasks.push(output(i)); }
Promise.all(tasks);
|
7. sleep方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| const sleep = (time) => new Promise(resolve => { setTimeout(resolve, time); })
async function print () { for (var i = 0; i < 10; i++) { await sleep(1000); console.log(i); } };
print();
|
8. bind方法
1 2 3 4 5 6 7
| function print() { for (var i = 0; i < 10; i++) { setTimeout(function(i) { console.log(new Date, i); }.bind(null,i), 1000 * i); } }
|