setTimeou倒计时不能按时处理及解决办法

        随着时间的推移, setTimeout 实际执行的时间和理想的时间差值会越来越大,这就不是我们预期的样子。

为什么 setTimeout 会不准时呢?

        首先javascript是单线程的,这意味着javascript一次只能执行一个任务,而且所有任务都在一个线程上运行。当浏览器执行javascript代码时,它必须处理各种任务,包括处理用户输入、渲染页面、执行javascript代码等。

任务队列

        为了处理不同类型的任务,浏览器引入了任务队列。任务队列分为两种主要类型:宏任务和微任务。setTimeout通常被认为是一个宏任务,promise.then()方法通常是微任务。

        当setTimeout的延迟时间到达时,它会将指定的回调函数添加到宏任务队列,等待执行。但是,由于javascript是单线程的,只有在前面的任务完成后,宏任务队列中的任务才会被执行。这就可能导致setTimeout的延迟时间被延长。

竞争执行

        另一个影响执行时间的因素是竞争执行。当浏览器中存在多个任务时,它们会根据优先级排队执行。例如,如果浏览器正在处理用户的点击事件、渲染页面以及执行javascript代码,setTimeout的回调函数必须等待前面的任务完成后才能执行。

最小延迟时间

        另一个需要注意的是,setTimeout最小延迟时间并不是准确的时间,根据规范,setTimeout的最小延迟时间是4ms,这意味着即使你将延时设置为0ms,它仍然需要等待至少4ms才能执行。

        总结来说,因为浏览器页面是有消息队列和事件循环来驱动的,创建一个 setTimeout 的时候是将它推进了一个队列,并没有立即执行,只有本轮宏任务执行完,才会去检查当前的消息队列是否有有到期的任务。

解决方法:

系统补偿定时器

?

        当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。

// 系统补偿定时器
function timer(delay) {
  const startDate = new Date().getTime();
  let count = 1;

  function instance() {
    const nowDate = new Date().getTime();
    const realTime = nowDate - startDate;
    count++;
    const diff = delay * count - realTime;
    console.log({diff, realTime});
    setTimeout(() => {
      instance();
    }, diff);
  }
  setTimeout(() => {
    instance();
  }, delay);
}
timer(500);