乐趣区

关于javascript:为什么要用-setTimeout-模拟-setInterval

在 JS 事件循环之宏工作和微工作中讲到过,setInterval 是一个宏工作。

用多了你就会发现它并不是准确无误,极其状况下还会呈现一些令人费解的问题。

上面咱们一一列举..

推入工作队列后的工夫不精确

定时器代码:

setInterval(fn(), N);

下面这句代码的意思其实是 fn() 将会在 N 秒之后被推入工作队列

所以,在 setInterval 被推入工作队列时,如果在它后面有很多工作或者某个工作等待时间较长比方网络申请等,那么这个定时器的执行工夫和咱们预约它执行的工夫可能并不统一。

比方:

let startTime = new Date().getTime();
let count = 0;
// 耗时工作
setInterval(function() {
  let i = 0;
  while (i++ < 1000000000);
}, 0);
setInterval(function() {
  count++;
  console.log(
    "与原设定的距离时差了:",
    new Date().getTime() - (startTime + count * 1000),
    "毫秒"
  );
}, 1000);
// 输入:// 与原设定的距离时差了:699 毫秒
// 与原设定的距离时差了:771 毫秒
// 与原设定的距离时差了:887 毫秒
// 与原设定的距离时差了:981 毫秒
// 与原设定的距离时差了:1142 毫秒
// 与原设定的距离时差了:1822 毫秒
// 与原设定的距离时差了:1891 毫秒
// 与原设定的距离时差了:2001 毫秒
// 与原设定的距离时差了:2748 毫秒
// ...

能够看进去,相差的工夫是越来越大的,越来越不精确。

函数操作耗时过长导致的不精确

思考极其状况,如果定时器外面的代码须要进行大量的计算(消耗工夫较长),或者是 DOM 操作。这样一来,花的工夫就比拟长,有可能前一次代码还没有执行完,后一次代码就被增加到队列了。也会到时定时器变得不精确,甚至呈现同一时间执行两次的状况。

最常见的呈现的就是,当咱们须要应用 ajax 轮询服务器是否有新数据时,必定会有一些人会应用 setInterval,然而无论网络情况如何,它都会去一遍又一遍的发送申请,最初的间隔时间可能和原定的工夫有很大的出入。

// 做一个网络轮询,每一秒查问一次数据。let startTime = new Date().getTime();
let count = 0;

setInterval(() => {
    let i = 0;
    while (i++ < 10000000); // 假如的网络提早
    count++;
    console.log(
        "与原设定的距离时差了:",
        new Date().getTime() - (startTime + count * 1000),
        "毫秒"
    );
}, 1000)
输入:// 与原设定的距离时差了:567 毫秒
// 与原设定的距离时差了:552 毫秒
// 与原设定的距离时差了:563 毫秒
// 与原设定的距离时差了:554 毫秒(2 次)
// 与原设定的距离时差了:564 毫秒
// 与原设定的距离时差了:602 毫秒
// 与原设定的距离时差了:573 毫秒
// 与原设定的距离时差了:633 毫秒

setInterval 毛病 与 setTimeout 的不同

再次强调,定时器指定的工夫距离,示意的是何时将定时器的代码增加到音讯队列,而不是何时执行代码。所以真正何时执行代码的工夫是不能保障的,取决于何时被主线程的事件循环取到,并执行。

setInterval(function, N)
// 即:每隔 N 秒把 function 事件推到音讯队列中

上图可见,setInterval 每隔 100ms 往队列中增加一个事件;100ms 后,增加 T1 定时器代码至队列中,主线程中还有工作在执行,所以期待,some event 执行完结后执行 T1 定时器代码;又过了 100ms,T2 定时器被增加到队列中,主线程还在执行 T1 代码,所以期待;又过了 100ms,实践上又要往队列里推一个定时器代码,但因为此时 T2 还在队列中,所以 T3 不会被增加(T3 被跳过),后果就是此时被跳过;这里咱们能够看到,T1 定时器执行完结后马上执行了 T2 代码,所以并没有达到定时器的成果。

综上所述,setInterval 有两个毛病:

  • 应用 setInterval 时,某些距离会被跳过;
  • 可能多个定时器会间断执行;

能够这么了解:每个 setTimeout 产生的工作会间接 push 到工作队列中;而 setInterval 在每次把工作 push 到工作队列前,都要进行一下判断(看上次的工作是否仍在队列中,如果有则不增加,没有则增加)。

因此咱们个别用 setTimeout 模仿 setInterval,来躲避掉下面的毛病。

来看一个经典的例子来阐明他们的不同:

for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
  }, 1000);
}

做过的敌人都晓得:是一次输入了 5 个 5;
那么问题来了:是每隔 1 秒输入一个 5?还是一秒后立刻输入 5 个 5?
答案是:一秒后立刻输入 5 个 5
因为 for 循环了五次,所以 setTimeout 被 5 次增加到工夫循环中,期待一秒后全副执行。

为什么是一秒后输入了 5 个 5 呢?
简略来说,因为 for 是主线程代码,先执行完了,才轮到执行 setTimeout。

当然为什么输入不是 1 到 5,这个波及到作用域的问题了,这里就不解释了。

那如果换成 setInterval 呢?

for (var i = 0; i < 5; i++) {setInterval(function() {console.log(i);
  }, 1000);
}

输入什么?
答案是:每 1 秒输入一个 5。
为什么输入一个 5?
是因为 setInterval 只在第 for 循环时被增加了,前面的并没有增加,也就是之前说的,setInterval 在每次把工作 push 到工作队列前,都要进行一下判断(看上次的工作是否仍在队列中,如果有则不增加,没有则增加)。

setTimeout 模仿 setInterval

综上所述,在某些状况下,setInterval 并不是很精确的。为了解决这些弊病,能够应用 settTimeout() 代替。具体实现如下:

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;
}

参考

  • 为什么要用 setTimeout 模仿 setInterval?
  • 用 settTimeout()代替 setInterval()
退出移动版