前言
为什么要用 setTimeout 模拟 setInterval 呢?
在 JavaScript 事件循环过程中,setInterval 是一个宏任务,它的定义是:按照指定的周期(以毫秒计)来调用函数或计算表达式
。
然而我们永久了会发现,很多时候,它并不会按照我们的期望来 work。
问题点
推入任务队列后的时间不准确
setInterval 用法:setInterval(fn(), N);
表示:fn() 将会在 N 秒之后被推入任务队列。
所以,在 setInterval 被推入任务队列时,如果它前面有很多任务或者某个任务等待的时间比较长,那么这个定时器的执行时间就会和我们预定它执行的时间并不一致。
多个定时器连续执行
某些间隔会被跳过
setTimeout
setInterval 指定的时间间隔,表示的是何时将定时器的代码添加到消息队列中,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取出并执行。
上图可见:
- setInterval 每隔 100ms 往队列中添加一个事件;
- 100ms 后,添加 T1 定时器代码至队列中,主线程中还有任务在执行,所以等待,some event 执行结束后执行 T1 定时器代码;
- 又过了 100ms,T2 定时器被添加到队列中,主线程还在执行 T1 代码,所以等待;
- 又过了 100ms,理论上又要往队列里推一个定时器代码,但由于此时 T2 还在队列中,所以 T3 不会被添加(T3 被跳过),结果就是此时被跳过;
- 这里我们可以看到,T1 定时器执行结束后马上执行了 T2 代码,所以并没有达到定时器的效果。
每个 setTimeout 产生的任务会直接 push 到任务队列中;而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。
setTimeout 模拟 setInterval
- 在前一个定时器执行完前,不会向队列插入新的定时器
- 保证定时器间隔
定义 interval 方法
let timer = null; interval(func, wait) { let interv = function(){ func.call(null); timer=setTimeout(interv, wait); }; timer= setTimeout(interv, wait); }
和 setInterval() 一样使用它
interval(function() {}, 20);
终止定时器
if (timer) { window.clearSetTimeout(timer); timer = null; }