前言

为什么要用 setTimeout 模拟 setInterval 呢?

在 JavaScript 事件循环过程中,setInterval 是一个宏任务,它的定义是:按照指定的周期(以毫秒计)来调用函数或计算表达式

然而我们永久了会发现,很多时候,它并不会按照我们的期望来 work。

问题点

推入任务队列后的时间不准确

setInterval 用法:setInterval(fn(), N);
表示:fn() 将会在 N 秒之后被推入任务队列。
所以,在 setInterval 被推入任务队列时,如果它前面有很多任务或者某个任务等待的时间比较长,那么这个定时器的执行时间就会和我们预定它执行的时间并不一致。

多个定时器连续执行

某些间隔会被跳过

setTimeout

setInterval 指定的时间间隔,表示的是何时将定时器的代码添加到消息队列中,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取出并执行。

js timeline

上图可见:

  • setInterval 每隔 100ms 往队列中添加一个事件;
  • 100ms 后,添加 T1 定时器代码至队列中,主线程中还有任务在执行,所以等待,some event 执行结束后执行 T1 定时器代码;
  • 又过了 100ms,T2 定时器被添加到队列中,主线程还在执行 T1 代码,所以等待;
  • 又过了 100ms,理论上又要往队列里推一个定时器代码,但由于此时 T2 还在队列中,所以 T3 不会被添加(T3 被跳过),结果就是此时被跳过;
  • 这里我们可以看到,T1 定时器执行结束后马上执行了 T2 代码,所以并没有达到定时器的效果。

每个 setTimeout 产生的任务会直接 push 到任务队列中;而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。

setTimeout 模拟 setInterval

  • 在前一个定时器执行完前,不会向队列插入新的定时器
  • 保证定时器间隔
  1. 定义 interval 方法

    let timer = null;
    interval(func, wait) {
        let interv = function(){
            func.call(null);
            timer=setTimeout(interv, wait);
        };
        timer= setTimeout(interv, wait);
     }
  2. 和 setInterval() 一样使用它

    interval(function() {}, 20);
  3. 终止定时器

    if (timer) {
      window.clearSetTimeout(timer);
      timer = null;
    }

结束