首页 > 试题广场 >

用setTimeout来实现setInterval

[问答题]

用setTimeout来实现setInterval

setInterval的缺点:

  1. 不能保证 “前一次回调任务结束时” 和 “后一次回调任务开始时” 之间的时间差为一个固定值。我们知道,循环定时器是每隔固定时间,就向消息队列添加一个回调任务。这就导致:当回调任务的执行时间,超过循环定时器设定的时间间隔时,相邻的两次回调任务会连续执行,中间就不再间隔时间了。
  2. 可能会导致任务丢失。这是setInterval的机制问题,当事件触发线程发现上一次循环的回调任务还没有执行时,会直接丢失当前回调任务。只要我们的回调任务耗时一些,这种意外就直接发生了。

先用setTimeout正确地实现一个setInterval:

/* 示范1 正确 */
function timeoutToInterval(func, time){
  function inside(){
    func();
    setTimeout(inside, time);
  }
  setTimeout(inside, time);
}
timeoutToInterval(()=>{
  console.log( '呀咩爹~' );
}, 1000)

将定时器放到需要执行任务的后面,这样就直接解决了上面的两个问题。

这里面有两个核心重点:

  • 为什么要用inside去包裹定时器和要执行的任务?直接用定时器包裹不行吗?

我们看一个不用inside包裹的实现方式:

/* 示范2 错误 */
function timeoutToInterval(func, time){
  setTimeout(function () {
    func();
    setTimeout(arguments.callee, time);
  }, time)
}
/*
  arguments.callee 等价于 function () {
    func();
    setTimeout(argumrnts.callee,  time);
  }
*/

看这种实现方式,用argument.callee来充当内层定时的回调函数,其值等于外层定时器的回调函数。

看起来好像也挺简便的,但是它会有兼容性问题,ES5的严格模式就严禁使用arguments.callee。

所以最好还是外层定时器的回调包在一个函数中,然后再由一个定时器去调用这个函数。

你可能还有一个问题,我把外层的回调包在一个函数中,那我在函数的定时器里面直接去调用的外层函数不就好了,形成一个递归,不是也能实现想要的效果?就像下面这样

/* 示范3 错误 */
function timeoutToInterval(func, time){
  function inside(){
    func();
    setTimeout(inside, time);
  }
  inside();
}
  • 为什么还要用一个定时器去启动这个函数呢?

答案很简单,因此两种实现方式在循环定时器刚启动时,效果是不一样的。示范3的方式,在循环定时器刚被调用时,回调函数会立即执行,而示范1则是在过了设定了间隔时间之后,才会执行第一次的回调任务。



【为什么要用setTimeout模拟setInterval ?_若水弹丸之地-CSDN博客】https://blog.csdn.net/b954960630/article/details/82286486

编辑于 2022-03-08 08:36:00 回复(0)