setTimeout的最小毫秒值是多少?

55

我想放置

var minValue = 0;
if ( typeof callback == 'function' ) {
    setTimeout( callback, minValue );
}

在使用JavaScript实现回调函数时,我遇到了这段代码。

但我发现现代浏览器和一些老旧的浏览器具有不同的最小超时值。

我知道零不能是最小值。

为了兼容性问题,在现代浏览器和一些老旧的浏览器中setTimeout的最小值是多少?


6
请参阅 MDN。尽管您指定的任何时间都不可靠。 - pimvdb
1
你可以随时编写一个测试... - Brad Christie
@BradChristie 你是如何对 JavaScript 代码进行单元测试的?采用 TDD 吗? - jwchang
4个回答

52

我认为10毫秒将成为所有浏览器中最可靠的最小值,因为我已经看到很多代码在使用它。

但是,HTML5的最小值为4毫秒

实际上,在2010年及以后发布的浏览器中,4毫秒已被HTML5规范指定,并且在各个浏览器之间保持一致。在此之前(Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2),嵌套超时的最小超时值为10毫秒。


3
抱歉,这已经不再是真的了(或者不再正确)。我只能在标准中找到一个关于4毫秒延迟的注释:“注意:计时器可以嵌套;然而,在五个这样的嵌套计时器之后,间隔被强制为至少四毫秒。” 进一步的好奇心让我发现了这篇很棒的帖子,介绍了Chrome如何处理它。 - Tobbe Brolin
@TobbeBrolin,无论在实现中是否如此,它仍然在规范中:“如果当前正在运行的任务是由setTimeout()方法创建的任务,并且超时时间小于4,则将超时时间增加到4。” - apsillers
3
然而,我并没有看到WHATWG规范中反映出这种语言。看起来W3C和WHATWG在这个问题上存在分歧。 - apsillers
1
因此,如果嵌套级别小于五,则在Chrome中最低值为“1毫秒”。 - twxia

11

最小时间为4毫秒(HTML5版本),在现代浏览器中,之前是10毫秒。请注意,这些时间永远不会100%准确。


2
4毫秒的延迟仅适用于从5个嵌套级别调用时。 - Kaiido
@Kaiido 但实践表明,您永远不会得到等于4毫秒的超时,它大约会是5毫秒。 - MaximPro
@MaximPro,不确定理解你的评论。我的观点是,与这个答案所述的相反,对于没有至少在第5层嵌套的超时,没有最小超时限制。因此,如果没有其他阻塞浏览器的情况,onclick=e=>setTimeout(fn,0)将在不到4ms的时间内执行fn。(在Chrome中有1ms的最小值,但他们已经删除了它) - Kaiido
@Kaiido,我只是强调嵌套计时器之间的超时时间(将嵌套5次或更多次)大约为5毫秒。 - MaximPro

5

新版本

没有保证的延迟时间。调度机制,包括setTimeout的最小毫秒数,取决于您的JavaScript环境和/或操作系统,因为它们不在官方ECMA规范中。

JavaScript引擎通常有多个队列,event queue不断处理。一旦当前脚本执行完毕(例如调用setTimeout(someCallback)的脚本),循环将按优先级顺序查看所有队列,并执行所有到期的挂起请求(包括someCallback或任何其他回调到setTimeout/setImmediate/process.nextTick等调用)。

一旦循环完成一次迭代,现代实现会设置等待最少的时间,直到下一个事件到期,或者直到外部IO事件唤醒它。例如,如果您有三个活动的setTimeout计时器,则它将等待这三个计时器上剩余时间的最小值。

警告:根据队列优先级,这可能会导致粒度变化。请注意,高分辨率系统计时器的成本很高。因此,如果您将某些内容安排到低优先级队列中,或者调度时间本身很长,则它们可能会选择使用较低性能的计时器进行“唤醒调用”,这意味着不同队列和延迟之间的情况可能会有很大差异。此外,正如我的测试所示,Chrome并不关心,并且即使在setTimeout中也使用(相当)高分辨率计时器。

这是我刚在Windows 10上快速运行的一些Chrome和Node测试。

代码:

function testScheduler(scheduler, ms) {
  function f() {
    console.log(`${scheduler.name} with ${ms ? `${ms} ms` : 'no'} delay:`, performance.now() - s);
  }
  var s = performance.now();
  scheduler(f, ms);
}

testScheduler(setTimeout, 0);
testScheduler(setTimeout, 1);
testScheduler(setTimeout, 5);
testScheduler(setTimeout, 100);
testScheduler(setImmediate);

实验结果:

结果概述:

  1. 数字可能会变化很大。
  2. Node 比 Chrome 不够准确(尽管在引擎方面使用相同的 V8)。

Chrome(2022/9):

(注意:在 Chrome 中,setImmediate 已被移除。)

setTimeout with 0 ms delay: 0.19999998807907104
setTimeout with 1 ms delay: 1.9000000357627869
setTimeout with 5 ms delay: 6.900000035762787
setTimeout with 100 ms delay: 110.5

Node@16(某些运行):

setImmediate with no delay: 0.23650002479553223
setTimeout with no delay: 5.476700007915497
setTimeout with 1 ms delay: 5.6164000034332275
setTimeout with 5 ms delay: 6.091899991035461
setTimeout with 100 ms delay: 103.40990000963211

Node@16(其他一些运行):

setImmediate with no delay: 1.2796000242233276
setTimeout with no delay: 19.26560002565384
setTimeout with 1 ms delay: 19.923299968242645
setTimeout with 5 ms delay: 21.00239998102188
setTimeout with 100 ms delay: 107.54070001840591

系统定时器

当队列不会立即运行,并且在迭代之间空闲时,最小的毫秒数由操作系统和硬件的时间片分辨率确定。通常最小的“可休眠”时间是进程被系统的调度算法分配另一个时间片所需的时间。

例如,在 Windows(XP之后),Node 可能正在使用Windows 的 sleep 系统调用,而 Chrome 的结果似乎表明它使用比这更高分辨率的定时器。


7
我必须承认这个答案没有让我信服,因为它与我所知道的关于JavaScript(大多数情况下)单线程事件驱动执行的不一致。你能指出任何参考资料表明任何JavaScript实现都调用sleep吗? - Davide
2
如果运行时实现正确,超时将在当前代码执行完成后才会运行 - 即使您指定时间为0,它仍将排队,具体取决于您的Javascript运行时如何实现。 - nnnnnn
2
我非常怀疑。特别是sleep|Sleep系统调用是否与此有关。我想JavaScript有自己的调度机制,粒度最终取决于语言特定浏览器实现。但是JavaScript是基于事件的,我认为它会在循环中检查事件,如果有任何计划和“过期”执行它们。这就是为什么它不准确(毫秒在这种情况下很大,因此应该期望准确性),也是为什么setTimeout()的时间参数应该有限制的原因。 - Iharob Al Asimi
@Davide,我终于开始重写它了。 - Domi

3
本文测试了Firefox、Safari和Opera,并绘制了性能图表:
http://ejohn.org/blog/analyzing-timer-performance/

Firefox 2、Opera和Safari的延迟时间最低为10ms

对于旧版浏览器,您可以像文章中那样进行测试。我刚刚进行了一项测试,使用IE6中的10毫秒间隔的setInterval,平均值为55毫秒setTimeout的结果似乎较低,约为35毫秒
我在Chromium中运行了测试,10毫秒的超时平均值约为11毫秒。我尝试使用4毫秒和1毫秒的间隔,两者的平均值都约为4.5毫秒。此外,请注意这些数字可能因操作系统而异。
如果您感兴趣,以下是测试代码:
<script>
// number of times to call setTimeout before calculating average
var ITERATIONS = 200;

window.onload = function()
{
    testTimeout(10, +new Date, 0, 0);
}

// calls setTimeout repeatedly at a specified interval, tracking the amount
// of time that passes between successive calls
function testTimeout(interval, last, sum, ii)
{
    var time = +new Date;
    var difference = time - last;
    sum += difference;
    if (ii % ITERATIONS == 1)
    {
        document.body.innerHTML = sum / ITERATIONS;
        sum = 0;
    }
    window.setTimeout(
        function() {
            testTimeout(interval, time, sum, ii + 1)
        }, interval);
}
</script>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接