在使用 setTimeout() 时,一般都会比设定的时间要稍微长一点,即使是 setTimeout(fn, 0) 也不会立即执行。造成这种问题的根本原因是: 最小延迟时间。
有很多因素会导致setTimeout的回调函数执行比设定的预期值更久,本节将讨论最常见的原因。
最小延时 >= 4ms
在浏览器中,setTimeout()/setInterval()
的每调用一次定时器的最小间隔是4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的setInterval的回调函数阻塞导致的。例如:
function cb() { f(); setTimeout(cb, 0); }
setTimeout(cb, 0);
setInterval(f, 0);
在Chrome 和 Firefox中, 定时器的第5次调用被阻塞了;在Safari是在第6次;Edge是在第3次。Gecko 从这个版本 version 56开始对 setInterval()
开始采用这样的机制(setTimeout()
已经实现,具体请参考以下内容)。
一直以来,不同浏览器中出现这种最小延迟的情况有所不同(例如Firefox) - 从其他地方调用了setInterval( ),或者在嵌套函数调用setTimeout( ) 时(嵌套级别达到特定深度时),都会出现超时延迟。
如果想在浏览器中实现0ms延时的定时器,你可以参考 这里 所说的{domxref("window.postMessage()")}}
未被激活的tabs的定时最小延迟>=1000ms
为了优化后台tab的加载损耗(以及降低耗电量),在未被激活的tab中定时器的最小延时限制为1S(1000ms)。
Firefox 从version 5 (see bug 633421开始采取这种机制,1000ms的间隔值可以通过 dom.min_background_timeout_value
改变。Chrome 从 version 11 (crbug.com/66078)开始采用。
Android 版的Firefox对未被激活的后台tabs的使用了15min的最小延迟间隔时间 ,并且这些tabs也能完全不被加载。
当 Web Audio API AudioContext 正在被用来播放音频的时候,Firefox 50不会再限制后台tabs的加载。 后续的Firefox 51 版本修正了这个问题,即使在没有音频播放的情况下,也不再限制后台tabs的加载。这个解决了一些软件应用在后台tabs中播放基于文本的音频( note-based) 时,无法去同步音频和画面的问题。
追踪型脚本的最小延时限制
从Firefox 55版本开始,追踪型脚本(例如 谷歌分析,或者其他的一些被Firefox 的 TP lists 识别为追踪型脚本的 外链URL脚本)是重点限制加载的对象。在当前正在使用的页面中,这个节流限制的延时依然是4ms。但是在后台tabs中,这个最小延时限制是10000ms(10s),这个限制会在文档第一次加载后的30s后生效。
控制这些行为的属性包含以下这些:
dom.min_tracking_timeout_value
: 4dom.min_tracking_background_timeout_value
: 10000dom.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的延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。
文章节选自:MDN