William's Blog

为什么setTimeout和setInterval的实际延时比设置的时间长


点击查看英文原文

延时比设置的时间长的原因

  setTimeout/setInterval延时超出预期时间是由多种原因造成的。常见的原因归结如下:

限制延时不得低于4ms

  在现代浏览器中,当回调函数嵌套达到一定级别或者在触发一定数量的setInterval之后,浏览器会限制setTimeout/setInterval回调函数的调用周期不得小于4ms。下面的示例代码会触发浏览器的限制动作:

function cb() {
    f();
    setTimeout(cb, 0);
}
setTimeout(cb, 0);
setInterval(f, 0);

  在Chrome和Firefox中,从第5次连续回调开始限制回调周期;Safari从第6次连续回调开始限制;Edge在第三次连续调用时开始限制。Gecko从版本 56开始对setInterval调用采取与上述限制策略(它已经像这样处理setTimeout调用)。
  浏览器触发限制行为的条件了略有差异,比如Firefox只要检测到setInterval调用就会触发限制行为而不是必须等到setInterval被调用一定次数。

非活动标签页中的延时被限制不得低于1000ms

  浏览器为了减小后台标签页中的加载量(同时减小电池消耗),限制后台标签页setTimeout/setInterval回调函数触发频率每秒不超过一次。
  Firefox从版本 5(详见bug 633421,通过dom.minbackgroundtimeoutvalue可以修改1000ms这一常量)开始执行这一限制行为,Chrome从版本 11开始执行该行为。
  在适用于安卓的Firefox浏览器中,从Firefox 14的[bug 736602](https://bugzilla.mozilla.org/show
bug.cgi?id=736602)开始,后台标签页的setTimeout/setInterval触发间隔被设置为15分钟,并且后台标签可以被完全卸载。
  注:当后台标签页使用Web Audio API AudioContext播放音频时,Firefox 50不对后台标签页进行限制。从Firefox 51开始,一旦后台标签页中出现了AudioContext,即使页面没有播放音频,Firefox也不限制该页面。

对追踪脚本的延时限制

  从Firefox 55开始,追踪脚本(比如Google Analytics,任何Firefox通过其TP表识别的脚本URL)受到了进一步的限制。追踪脚本在前台页面运行时,其最小时间间隔仍然设置为4ms。但是当追踪脚本在后台标签页运行时,其最小延时被限制为10s,这将在文档首次加载之后30s生效。
  控制这一限制行为的字段如下:

  1. dom.mintrackingtimeout_value: 4
  2. dom.mintrackingbackgroundtimeoutvalue: 10000
  3. dom.timeout.trackingthrottlingdelay: 30000

晚延时

  除了浏览器的限制策略之外,当页面(或者操作系统/浏览器本身)忙于其他任务时,setTimeout/setInterval回调函数也会比预设的时间晚触发。值得关注的一点是,setTimeout/setInterval回调函数或者代码片段不能被执行直到调用setTimeout()的主线程结束。比如:

function foo() {
  console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');

  要写入控制台的结果:

After setTimeout
foo has been called

  这是因为即使setTimeout被调用时延时设置为0,它也会被放置在一个执行队列中并且计划在下一个时机被执行而不是立即被执行。当前正在被执行的代码必须在执行队列中的任务被执行前执行完成,因此最终的执行顺序可能不符合预期。
  为了在现代浏览器中实现0ms延时,可以参考这篇文章介绍的使用postMessage()的方案。


William

本博客作者 William 现任职于北京贝壳找房,从事web前端开发相关工作。
您可以通过Email与他取得联系