1.setInterval机制:
- 延时一段时间后,将任务push到任务队列中排队执行;
- 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。
基于该原理,会导致两个问题:
- 前一任务结束到当前任务开始的时间间隔与设置的delay值不符。
- 可能出现某些任务被跳过的情况
(1)任务添加的时间间隔误差较大的情况
setInterval(foo, delay)
//当foo执行消耗800ms,delay为1000ms时,前一个任务执行结束到后一个任务开始执行只间隔了200ms
假如定时器里面的代码需要进行大量的计算(耗费时间较长),或者是 DOM 操作。这样一来,花的时间就比较长,有可能前一次代码还没有执行完,后一次代码就被添加到队列了。也会到时定时器变得不准确。
// 无耗时较长的同步代码时:
let end = new Date().getTime();
let start = new Date().getTime();
setInterval(function () {
start = new Date().getTime();
console.log("上一任务结束——当前任务开始:", start - end, "ms");
end = new Date().getTime();
}, 1000);
// 执行结果(与预期相符):
// 上一任务结束——当前任务开始: 1005 ms
// 上一任务结束——当前任务开始: 997 ms
// 上一任务结束——当前任务开始: 1002 ms
// 上一任务结束——当前任务开始: 998 ms
// 上一任务结束——当前任务开始: 1000 ms
// 上一任务结束——当前任务开始: 1000 ms
// 上一任务结束——当前任务开始: 1002 ms
// 上一任务结束——当前任务开始: 1000 ms
(2)可能出现某些间隔会被跳过
上图可见,setInterval 每隔 100ms 往队列中添加一个事件;100ms 后,添加 T1 定时器代码至队列中,主线程中还有任务在执行,所以等待,some event 执行结束后执行 T1 定时器代码;又过了 100ms,T2 定时器被添加到队列中,主线程还在执行 T1 代码,所以等待;又过了 100ms,理论上又要往队列里推一个定时器代码,但由于此时 T2 还在队列中,所以 T3 不会被添加(T3 被跳过),结果就是此时被跳过;这里我们可以看到,T1 定时器执行结束后马上执行了 T2 代码,所以并没有达到定时器的效果。
没有耗时较长时间的任务时:
let startTime = new Date().getTime();
let count = 0;
setInterval(function () {
count ++;
//显示当前任务实际执行时间与计划执行时间的差值
console.log(
"与原设定的间隔时差了:",
new Date().getTime() - (startTime + count * 1000),
// a,
"毫秒"
);
}, 1000);
任务中存在耗时较长的操作:
let startTime = new Date().getTime();
let count = 0;
setInterval(function () {
// 耗时较长的操作
let i = 0;
while (i++ < 2500000000);
count ++;
//显示当前任务实际执行时间与计划执行时间的差值
console.log(
// "count",
// count,
"与原设定的间隔时差了:",
new Date().getTime() - (startTime + count * 1000),
// a,
"毫秒"
);
}, 1000);
解决方案
可使用setTimeout 模拟 setInterval。
setTimeout 机制:产生的任务会直接 push 到任务队列中。
function mySetInterval(fun, delay) {
let timer = null
function interval() {
//fun中的同步代码执行完之后,再开始定时
fun()
setTimeout(interval, delay);
}
interval()
return {
cancel: () => {
clearTimeout(timer)
}
}
}
mySetInterval(() => console.log('mySetInterval'), 1000)