JavaScript setTimeout 如此不准确的原因是什么?

IT技术 javascript settimeout
2021-01-31 20:19:35

我在这里得到了这个代码:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    if(currentDate - date >= 1000) {
         console.log(currentDate, date);
         console.log(currentDate-date);
    }
    else {
       console.log("It was less than a second!");
       console.log(currentDate-date);
    }
}, 1000);

在我的计算机中,它始终正确执行,控制台输出为 1000。有趣的是,在其他计算机上,相同的代码,超时回调在不到一秒的时间内启动,差异currentDate - date在980和998之间。

我知道存在解决这种不准确性的库(例如,Tock)。

基本上,我的问题是:在给定的延迟时间内没有触发的原因setTimeout什么会不会是电脑速度太慢,浏览器自动尝试适应速度慢,然后触发事件?

PS:下面是在Chrome JavaScript控制台中执行的代码和结果截图:

在此处输入图片说明

4个回答

应该不是特别准确。有许多因素会限制浏览器执行代码的速度;引用自MDN

除了“钳位”之外,当页面(或操作系统/浏览器本身)忙于其他任务时,超时也可以稍后触发。

换句话说,setTimeout通常的实现方式只是在给定的延迟后执行,并且一旦浏览器的线程可以自由执行它。

但是,不同的浏览器可能会以不同的方式实现它。以下是我做过的一些测试:

var date = new Date();
setTimeout(function(e) {
    var currentDate = new Date();
    console.log(currentDate-date);
}, 1000);

// Browser Test1 Test2 Test3 Test4
// Chrome    998  1014   998   998
// Firefox  1000  1001  1047  1000
// IE 11    1006  1013  1007  1005

也许来自 Chrome 的 < 1000 次可能是由于Date类型不准确,或者也许是因为 Chrome 使用不同的策略来决定何时执行代码——也许它试图将它放入最近的时间段,即使超时延迟尚未完成。

简而言之,setTimeout如果您期望可靠、一致的毫秒级计时,则不应使用

一般来说,当尝试以高于 50 毫秒的精度执行事物时,计算机程序是非常不可靠的。这样做的原因是,即使在八核超线程处理器上,操作系统通常也要处理数百个进程和线程,有时甚至数千个或更多。操作系统通过安排所有多任务处理来使所有多任务工作一个接一个地获得 CPU 时间,这意味着它们“最多只有几毫秒的时间来做他们的事情”。

这意味着,如果您将超时设置为 1000 毫秒,那么当前浏览器进程在该时间点甚至不会运行的可能性远非小,因此浏览器直到 1005、1010 或甚至 1050 毫秒,它应该执行给定的回调。

通常这不是问题,它会发生,而且很少是最重要的。如果是,所有的操作系统提供内核级的定时器,远远超过1毫秒精确,并允许开发人员在执行代码正是在正确的时间点。然而,JavaScript 作为一个高度沙盒的环境,无法访问这样的内核对象,浏览器也不会使用它们,因为它理论上可以允许某人通过仔细构建使其他人挨饿的代码从网页内部攻击操作系统的稳定性。通过用很多危险的计时器淹没它来消除线程。

至于为什么测试产生 980 我不确定 - 这将取决于您使用的浏览器和 JavaScript 引擎。然而,我可以完全理解浏览器是否只是手动向下更正系统负载和/或速度,确保“平均延迟仍然是正确的时间” - 从沙盒原理到只是很有意义在不给系统其余部分带来潜在负担的情况下估算所需的时间量。

事实上,这个问题并不是 JavaScript 特有的。对于执行解释性语言的浏览器而言,准确度为 50 毫秒或更短,我认为我们已经相当不错了。
2021-03-22 20:19:35

如果我误解了这些信息,请有人纠正我:

根据John Resig关于跨平台性能测试不准确的帖子(重点是我的)

由于系统时间不断四舍五入到最后一次查询的时间(每间隔大约 15 毫秒),性能结果的质量受到严重影响。

因此,与系统时间相比,两端最多有 15 毫秒的延迟。

这是问题的一方面。另一个问题是浏览器的异步特性,因此根据队列中的其他内容,超时可能会被搁置以处理其他作业。
2021-03-31 20:19:35

我有过类似的经历。
我正在使用这样的东西:

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000));
setTimeout(function ()
{
    CountDownClock(ElementID, RelativeTime);
}, iMillSecondsTillNextWholeSecond);//Wait until the next whole second to start.

我注意到它会每隔几秒跳过一秒钟,有时会持续更长时间。
但是,我仍然会在 10 或 20 秒后发现它正在跳过,而且它看起来很摇摇晃晃。
我想,“也许超时太慢了还是在等待其他东西? ”。
然后我意识到,“也许它太快了,浏览器管理的计时器关闭了几毫秒?

在将 +1 MilliSeconds 添加到我的变量后,我只看到它跳过一次。
为了安全起见,我最终添加了 +50ms。

var iMillSecondsTillNextWholeSecond = (1000 - (new Date().getTime() % 1000) + 50);

我知道,这有点笨拙,但我的计时器现在运行顺畅。:)