昨天首页文章看到了一道面试题,关于setTimeout的设置为0ms时的触发时机问题,答案写“由设定的延迟时间决定”。但其实这个回答是有问题且不负责任的,基本等于没回答。故有了这篇MDN文档上关于这个问题的翻译(MDN原翻译貌似和原文有点脱节)。
翻译自developer.mozilla.org/en-US/docs/…
触发时间限制
在现代浏览器中,当setTimeout或setInterval的回调函数在这两个函数回调中嵌套触发时(嵌套层数会有限制),会触发最少每4ms执行一次回调。
在Chrome和Firefox中,第五次成功的回调将会被限制;Safari将会在第六次被限制;而Edge中将会在第三次就被限制。Gecko在版本56才开始对setInterval有这种限制,setTimeout之前一直按照标准。
过去一些版本的浏览器对这种限制的实现会有些不同。例如在以前版本的Firefox,setInterval在任何地方调用都会有4ms的限制,但是setTimeout则只有在一定嵌套层数后才会有限制。
如果实在想在现代浏览器中达到0ms延迟触发回调,可以使用window.postMessage()
最小延迟的常量DOM_MIN_TIMEOUT_VALUE为4ms(在Firefox中,存储在dom.min_timeout_value中),限制触发的嵌套层数DOM_CLAMP_TIMEOUT_NESTING_LEVEL为5。
4ms这个时间是HTML规范中规定的,然后在2010及往后的规范中皆是如此。之前的一些浏览器(Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2)设定限制为10ms。
未激活的标签页限制触发时间大于1000ms
为了减少未激活状态tab的加载(减少功耗),触发事件被限制每不小于1000ms。Firefox在版本5后实现了该特性(并保存常量引用在dom.min_background_timeout_value),Chrome则在版本11后实现。
安卓平台的Firfox在版本14后,将未激活tab的任务时延设置为15分钟,并且隐藏的tab有可能会完全停止加载。
值得注意的是,版本50的Firefox对于Web Audio API中AudioContext接口正在播放声音的tab将不会再做限制。版本51的Firefox继承了这一设定并在此基础上规定,即使没有声音正在播放声音,只要调用了即可。这样的作法其实是为了解决存一些和文本同步的音乐在隐藏tab后不同步问题。
追踪型脚本的最小时延
从Firefox 55起,追踪型脚本(比如Google Analytics,或者其他存在于Firefox名单中的追踪脚本),都将被进一步限制。当在前台运行时,不受影响。但是当在后台运行时,最小时延将延长到10000ms,这个效果将在文档加载完成30秒后生效。
涉及到这个行为的一些引用:
- dom.min_tracking_timeout_value: 4
- dom.min_tracking_background_timeout_value: 10000
- dom.timeout.tracking_throttling_delay: 30000
延迟执行时延任务
除了时延限制外,试验任务在主线程正在执行任务时,时延任务也会推迟执行。一个非常重要的情况就是,只有当调用setTimeout的现成运行结束时,时延任务才会执行,例如:
function foo() {
console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');
打印结果为:
After setTimeout
foo has been called
这是因为即使setTimeout以0ms进行调用,但是也会被推入到异步队列中,等待下一次事件循环执行,所以并不是立即执行。当前代码必须执行完成后才会有机会执行异步队列里的任务,所以打印结果才和想象中的有些出入。
页面加载时推迟时延任务
从Firefox 66开始,计时器任务将会推迟到当前tab加载完成后执行。并将会在主线程处于空闲状态时(类似于window.requestIdleCallback)或load事件触发时执行