JavaScript——定时器为什么是不精确的

2024-08-16 13:27:23 浏览数 (4)

前言

  • 运行机制
  • 实际探究

步骤

简要回答

首先,我们要知道 setInterval 的运行机制,setInterval 属于宏任务,要等到一轮同步代码以及微任务执行完后才会走到宏任务队列,但是前面的任务到底需要多长时间,这个我们是不确定的

等到宏任务执行,代码会检查 setInterval 是否到了指定时间,如果到了,就会执行 setInterval,如果不到,那就要等到下次 EventLoop 重新判断

当然,还有一部分不确定的因素,比如 setInterval 的时间戳小于 10ms,那么会被调整至 10ms 执行,因为这是 setInterval 设计及规定,当然,由于其他任务的影响,这个 10ms 也会不精确

还有一些物理原因,如果用户使用的设备处于供电状态等,为了节电,浏览器会使用系统定时器,时间间隔将会被调整至 16.6ms

深入探究版

1.超时限制为>=4ms

在现代浏览器中,由于回调嵌套(嵌套级别至少为特定深度)或者经过一定数量的连续间隔而触发连续调用时,setTimeout/setInterval调用至少每4ms被限制一次

代码语言:javascript复制
function f(){}
function cb(){
    f()
    setTimeout(cb,0)
}
setTimeout(cb,0)
  • 在Chrome和Firefox 第五次连续的调用就会被限制
  • Safari锁定了第六次通话
  • Edge在第三次
  • Gecko在version56已经这样开始尝试setInterval(对setTimeout也一样) 。In Chrome and Firefox, the 5th successive callback call is clamped; Safari clamps on the 6th call; in Edge its the 3rd one. Gecko started to treat setInterval() like this in version 56 (it already did this with setTimeout(); see below).

从历史上来看,某些浏览器在执行此节流方式有所不同了,在setInterval从任何地方的调用上,或者在setTimeout嵌套级别至少达到一定深度的情况下调用嵌套时,要想在现代浏览器实现0毫秒延迟可以使用postMessage

注意:最小延迟DOM_MIN_TIMEOUT_VALUE为4ms,同时DOM_CLAMP_TIMEOUT_NESTING_LEVEL是5(dom固定超时嵌套级别)

2.在非活动tab卡,超时限制为>=1000ms

为了减少背景选项卡的负载(和相关的资源使用),在不活动的资源卡将超时限制为1000ms以下

firefox从版本5开始实施该行为(可通过dom.min_background_timeout_value首选项调整1000ms常量)。Chrome从版本11开始实现该行为,自Firefox 14中出现错误736602以来,Android版Firefox的背景标签使用的超时值为15分钟,并且背景标签也可以完全卸载

3.限制跟踪超时脚本

自Firefox 55起,跟踪脚本(例如Google Analytics(分析),Firefox通过其TP列表将其识别为跟踪脚本的任何脚本URL )都受到了进一步的限制。在前台运行时,节流最小延迟仍为4ms。但是,在后台选项卡中,限制最小延迟为10,000毫秒(即10秒),该延迟在首次加载文档后30秒生效。

控制此行为的首选项是:

  • dom.min_tracking_timeout_value:4
  • dom.min_tracking_background_timeout_value:10000
  • dom.timeout.tracking_throttling_delay:30000
4.逾期超时

除了固定值意外,当页面(或OS /浏览器本身)忙于其他任务时,超时还会在以后触发。要注意的一个重要情况是,直到调用的线程setTimeout()终止,函数或代码段才能执行。例如:

代码语言:javascript复制
function foo() {
  console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');
// After setTimeout    foo has been called

这是因为即使setTimeout以零的延迟被调用,它也被放置在队列中并计划在下一个机会运行。不是立即。当前执行的代码必须在执行队列中的功能之前完成,因此生成的执行顺序可能与预期的不同

1 人点赞