Javascript setInterval定时会慢慢偏离保持准确的方向

Javascript setInterval定时会慢慢偏离保持准确的方向,javascript,setinterval,Javascript,Setinterval,似乎当IsetInterval持续1000ms时,它实际上每隔1001ms左右启动一次函数。这导致运行时间越长,时间漂移越慢 var start; var f = function() { if (!start) start = new Date().getTime(); var diff = new Date().getTime() - start; var drift = diff % 1000; $('<li>').text(drift + &q

似乎当I
setInterval
持续1000ms时,它实际上每隔1001ms左右启动一次函数。这导致运行时间越长,时间漂移越慢

var start;
var f = function() {
    if (!start) start = new Date().getTime();
    var diff = new Date().getTime() - start;
    var drift = diff % 1000;
    $('<li>').text(drift + "ms").appendTo('#results');
};

setInterval(f, 1000);
var启动;
var f=函数(){
如果(!start)start=new Date().getTime();
var diff=new Date().getTime()-start;
var漂移=差异%1000;
$(“
  • ”).text(漂移+ms”).appendTo(“#结果”); }; 设定间隔(f,1000);
  • 运行时,立即显示不准确

    • 0毫秒
    • 1ms
    • 2ms
    • 3ms
    • 4ms
    • 5ms
    • 5ms
    • 7毫秒
    • 8毫秒
    • 9ms
    • 9ms
    • 10毫秒
    亲自去看看吧:


    那么有没有更准确的方法来计时呢?或者一种使
    setInterval
    的行为更准确的方法?

    通过谷歌搜索,你会发现
    setInterval
    settimeout
    都不会在你告诉它的确切指定时间执行代码。具有
    setInterval(f,1000)它将在执行前至少等待1000毫秒,而不是完全等待1000毫秒。其他进程也在等待轮到它们使用CPU,这会导致延迟。如果你需要一个精确的计时器,计时时间为1秒。我会使用较短的时间间隔,比如50毫秒,并将其与开始时间进行比较。但我不会低于50毫秒,因为浏览器有一个最小间隔

    以下是一些参考资料:

    “为了了解计时器如何在内部工作,有一个重要的概念需要探讨:计时器延迟不保证。因为浏览器中的所有JavaScript都在单线程异步事件(如鼠标单击和计时器)上执行只有在执行过程中出现漏洞时才会运行。最好用图表来说明这一点,如下图所示:“摘自:

    Chrome和Chrome提供了平均超过41毫秒的时间间隔,这足以让第二个时钟在一分钟内明显变慢。Safari的速度略低于41毫秒,性能优于Chrome,但仍然不太好。我在Windows XP下读取了这些数据,但Chrome在Windows 7下的性能实际上更差,平均间隔约为46毫秒

    我看不到像脚本所报告的那样大的漂移:

    我正在运行该脚本。现在(Chrome,Win 7)我看到:

    240.005s中的240次呼叫等于0.99979次呼叫/秒

    事实上,我已经看到漂移上升到.007s,然后下降到.003s。我认为你的测量技术是有缺陷的


    在Firefox中,我看到它漂移得更厉害(+/-8ms),然后在下一次运行中进行补偿。大多数时候,我在Firefox中看到“每秒1.000000次呼叫”。

    我想我可能已经找到了解决方案。我想,如果你能测量它,你就可以补偿它,对吗

    因此,如果它比应该的时间晚1毫秒、2毫秒甚至10毫秒被调用,那么下一次调用计划将对此进行补偿。只要不准确度仅限于每次调用,但时钟永远不会丢失时间,那么这应该可以很好地工作



    现在我把它包装成了一个全局
    accurateInterval
    函数,它几乎可以替代
    setInterval

    这里是另一个自动校正间隔。这个间隔被设置为一个较短的时间段,然后它会等到至少一秒钟后才触发。它不会总是在1000毫秒后触发(似乎在0-6ms延迟范围内),但它会自动校正,不会漂移

    编辑: 更新为使用调用
    setTimeout
    而不是
    setInterval
    ,否则在大约1000次迭代后,它可能会做一些奇怪的事情

    var start, tick = 0;
    var f = function() {
        if (!start) start = new Date().getTime();
        var now = new Date().getTime();
        if (now < start + tick*1000) {
            setTimeout(f, 0);
        } else {
            tick++;
            var diff = now - start;
            var drift = diff % 1000;
            $('<li>').text(drift + "ms").appendTo('#results');
            setTimeout(f, 990);
        }
    };
    
    setTimeout(f, 990);
    
    var开始,勾选=0;
    var f=函数(){
    如果(!start)start=new Date().getTime();
    var now=new Date().getTime();
    如果(现在<开始+勾选*1000){
    setTimeout(f,0);
    }否则{
    tick++;
    var diff=现在-开始;
    var漂移=差异%1000;
    $(“
  • ”).text(漂移+ms”).appendTo(“#结果”); 设置超时(f,990); } }; 设置超时(f,990);

  • 您可以使用此函数使呼叫保持接近预期的计划。它使用
    setTimeout
    并根据经过的时间计算下一次呼叫时间

    函数setApproxInterval(回调,间隔){
    让运行=真
    const startTime=Date.now()
    常量循环=(n次运行)=>{
    const targetTime=n次运行*间隔+开始时间
    const timeout=targetTime-Date.now()
    设置超时(()=>{
    如果(正在运行){
    回调函数()
    循环(n次运行+1)
    }
    },超时)
    }
    环路(1)
    return()=>running=false
    }
    函数ClearApproxinInterval(停止间隔){
    停止间隔()
    }
    //示例用法
    const testStart=Date.now()
    const interval=setApproxInterval(()=>console.log(`${Date.now()-testStart}ms`),1000)
    
    setTimeout(()=>ClearApproxinInterval(interval),10000)
    通过更改Google Chrome上的选项卡,可以轻松重现setInterval或setTimeout中的不精确性。 为了处理这些情况,您可能需要考虑在用户处于另一个选项卡时设置一个条件

    setTimeout(function() {
        if (!document.hasFocus()) {
            //... do something different, because more than 1 second might have passed
        }
    }, 1000);
    

    您可以使用“setTimeout()"处理程序显式重置其下一个间隔的方法,在错误中计算,但实际上你不能依赖浏览器中的精确性。我想知道,这里的用例是什么。有趣的观察javascript的解释性质,加上浏览器的差异,将使这一级别的精确性变得困难。你可以测试函数体运行时间过长会增加你的漂移。在安排下一个函数之前,
    setInterval
    是否会考虑函数中经过的时间?相关问题50ms是20fps;Chrome caps回调是200fps,或5ms;Firefox和IE的运行速度至少会达到250fps(我不确定它们是否受到限制).很有趣,但超高的滴答声率似乎是一个强力解决方案
    var start, tick = 0;
    var f = function() {
        if (!start) start = new Date().getTime();
        var now = new Date().getTime();
        if (now < start + tick*1000) {
            setTimeout(f, 0);
        } else {
            tick++;
            var diff = now - start;
            var drift = diff % 1000;
            $('<li>').text(drift + "ms").appendTo('#results');
            setTimeout(f, 990);
        }
    };
    
    setTimeout(f, 990);
    
    setTimeout(function() {
        if (!document.hasFocus()) {
            //... do something different, because more than 1 second might have passed
        }
    }, 1000);