为什么要用setTimeout模拟setInterval?

3,601 阅读3分钟

setTimeout和setInterval基本用法:

  1. 指定延迟时间后调用函数
  2. 以指定周期调用函数

使用setInterVal:

function doStuff(){
// 此处为需要执行一段时间T的代码
}
setInterVal(doStuff, 100);

下来看如何使用setTimeout模拟setInterval:

function tick() {
    doStuff();
    setTimeout(tick, 100);
}
tick();

看下正常情况下两者的区别:

setInterval每个定时器之间的间隔是100ms,而setTimeout每隔100ms执行一次doStuff,所以每个定时器之间的间隔是100 + T(doStuff执行时间为T);这个T就是本文的关键了。

  • 如果T可以忽略的话,两者的效果是基本相同的。
  • T <= 100时,setInterval定时器间隔100,setTimeout定时器间隔100+T。
  • 如果T > 100,setTimeout依然如上图,两个定时器之间间隔100+T。 那么setInterval呢?

先看下图:

在0ms时,定时器1开始进入宏任务队列;100ms时,定时器1开始执行doStuff1,队列为空,定时器2进入队列;200ms时,因为定时器2还在队列中,所以定时器3被跳过。浏览器不会同时创建两个相同的间隔计时器。 300ms时,定时器2已经开始执行,队列为空,定时器4进入队列。以此类推~

下面我们用代码验证下。T设置为140ms。 我们让定时器运行5次,按照上述理解,总运行时间应该是:100+5*140 = 800ms。 代码如下:

      let i = 0;
      console.time("总时间");

      function doStuff() {
        console.log("delay");
        dead(140);
        console.timeEnd("测试");
      }

      function dead(delay) {
        var start = new Date().getTime();
        while (new Date().getTime() < start + delay);
      }

      let timer = setInterval(() => {
        i++;
        if (i > 4) {
          clearInterval(timer);
          setTimeout(() => {
            console.timeEnd("总时间");
          }, 0);
        }
        console.log("interval start");
        console.time("测试");
        doStuff();
      }, 100);

运行结果:

可以看出定时器运行了5次,总时间的确为800ms。 如果doStuff中的代码是异步的呢?比如像我们常用的请求接口。 简单起了个本地服务器,140ms返回结果。代码很简单就不上了。下面是js代码:

      let i = 0;
      console.time("总时间");

      function delay() {
        fetchData();
      }

      function fetchData() {
        return fetch("/home", {
          method: "POST"
        })
          .then(res => {
            console.log("res>>>>>>");
          })
      }

      let timer = setInterval(() => {
        i++;
        if (i > 4) {
          clearInterval(timer);
          setTimeout(() => {
            console.timeEnd("总时间");
          }, 0);
        }
        delay();
      }, 100);

控制台输出:

可以看出总时间是500ms,请求接口的异步代码并不会阻塞定时器。这个也很好理解,定时器中的同步代码会直接进入队列,异步代码注册事件,完成后进入队列;所以当异步代码注册事件后,这个定时器就执行完了,并不是等异步代码回来后这个定时器才算结束,不然5次定时器的总时间就是800ms了。

结论:

  • setInterval只是在特定时间点将代码推入队列,如果已有定时器在队列中,则会跳过。浏览器不会同时创建两个相同的间隔计时器。
  • setInterval中的异步代码不会阻塞创建新的定时器。

参考文章: