浏览器如何确定setInterval应该使用的时间?

Blo*_*ust 6 javascript setinterval

通常情况下,浏览器在某些情况下会修改setInterval使用的实际时间间隔,甚至超过最小钳位.例如,我有以下代码:

function start() {
    window.setInterval(function() {
        update();
    }, 1);
}

lastTime = new Date;
numFrames = 0;
lastFrames = 0;

function update() {
    numFrames++;
    if (new Date - lastTime >= 1000) {
        lastFrames = numFrames;
        numFrames = 0;
        lastTime = new Date;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,lastFrames将给出我们在大约过去的秒数上的帧数.在Chrome,Firefox和Safari中使用时,此代码不会在一毫秒内运行.当然,每个浏览器在setInterval调用之间都有一个任意的最短时间,所以这是可以预料的.但是,随着页面继续运行,即使选项卡仍处于焦点,帧速率也将继续降低.我发现修复此问题的唯一方法是让浏览器执行某些操作.沿着这些方向的东西似乎使浏览器setInterval尽可能快地运行:

function start() {
    window.setInterval(function() {
        update();
    }, 1);
}

lastTime = new Date;
numFrames = 0;
lastFrames = 0;

function update() {
    numFrames++;
    if (new Date - lastTime >= 1000) {
        lastFrames = numFrames;
        numFrames = 0;
        lastTime = new Date;
    }

    //doIntensiveLoop, processing, etc.
}
Run Code Online (Sandbox Code Playgroud)

因此,我的问题是:浏览器在寻找什么来证明setInterval更接近我的要求?

编辑:HTML5规范说,浏览器不应允许setInterval以低于4毫秒的间隔运行.

Mat*_*eer 5

setInterval并且setTimeout根本不准确,并且不是设计成的.JavaScript是单线程的,所以setTimeout/setInterval基本上说"把这块代码放在运行队列中.当你到达它时,如果已经过了足够的时间,那么执行它,否则将它重新放回队列中,然后再试一次" .

如果你将setInterval设置为4毫秒,但是应用程序中的东西需要10毫秒才能运行,那么setInterval就无法在4毫秒内运行,最好它会以10毫秒的间隔运行.

如果这是为了动画/游戏目的,那么试试requestAnimationFrame.我不知道它是如何实现的,但它承诺的一件事是更精确的计时.


Bea*_*rtz 4

我认为首先,我们必须问自己我们对区间函数的期望:

  • 他们必须维护上下文:无法可靠地增加计数器的时间间隔将是相当灾难性的

  • 他们应该执行函数中的任何内容,该函数的优先级高于执行间隔(同样,在我们再次递增之前,计时器必须上升)

  • 它应该有一个与我们的其余 js 代码分开的执行堆栈(我们不想等待那些计时器完成其业务,直到其余的开始执行);

  • 他们应该知道自己所处的上下文,无论它有多大(例如,我们希望能够在计时器函数中使用 jQuery)。

因此,正如Matt Greer上面所说,间隔在设计上并不精确,这主要是因为我们并不真正期望它们是精确的,而是在给定时间可靠地执行代码。

如果您查看 Chromium 实现,您会发现 setTimeout 和 setInterval 的实现基于DOMTimer::install,它传递执行上下文、操作以及计时器是否为单次触发

它被传递给 RunloopTimer,它在系统计时器的帮助下执行循环(如您在此处看到的)

(顺便说一句,Chromium 安装的间隔至少为 10 毫秒,如您在此处看到的)

操作的每次执行都在此处处理,自上次执行超过或低于特定时间限制以来,它不会进行任何断言。

相反,它只是通过减慢在给定时间间隔内使用太多资源/运行太慢的计时器来断言计时器嵌套级别不会太深:

if (m_nestingLevel >= maxTimerNestingLevel)
            augmentRepeatInterval(minimumInterval - repeatInterval());
    }
Run Code Online (Sandbox Code Playgroud)

AugmentRepeatInterval 只是在间隔中添加更多毫秒:

void augmentRepeatInterval(double delta) { augmentFireInterval(delta); m_repeatInterval += delta; }
Run Code Online (Sandbox Code Playgroud)

那么,我们可以得出什么结论呢?

  • 测量间隔或超时的时间准确性是浪费时间。您应该并且可以关心的是,不要将函数中要执行的操作的间隔设置得太低。浏览器将尽最大努力及时执行您的间隔和超时,但它不能保证准确的时间。

  • 间隔执行取决于环境、浏览器、实现、版本、上下文、操作本身等等。它并不意味着精确,如果你想用 setTimeout 或 setInterval 编写精确的东西,你要么现在疯了,要么以后会疯。

  • 您说在您的代码中,当您向函数添加大量执行时,计时器变得更加准确。这可能有不同的原因(也许它获得更多内存、更多独占 CPU 时间、更多工作线程等等)。我对此非常感兴趣,但您尚未提供执行繁重执行的代码。因此,如果您想得到一些答案,请提供代码,因为没有代码就很难做出任何假设。

  • 但无论是什么让你的间隔跑得更及时,它都不可靠。如果您开始在不同的系统上进行测量,您很可能会得到各种不同的结果。


更新

除此之外,浏览器 js 引擎可能会优化那些不会产生任何结果的代码(不执行、只执行一次、以更好的方式执行)。让我根据你的例子给出一个例子(所有的事情都是在 chromium 中执行的):

function start() {
  window.setInterval(function() {
    update();
  }, 1);
}

lastTime = new Date;
 numFrames = 0;
lastFrames = 0;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  for (var i=0; i < 1000000; i++) { var k = 'string' + 'string' + 'string' }
}
Run Code Online (Sandbox Code Playgroud)

你会发现点击后的第一次执行start将花费很长时间,而进一步的执行则不需要(至少在 webkit 中)。这是因为迭代中的代码不会改变任何内容,浏览器在第一次执行后会识别出这一点,并且不再执行它。

让我们看看如果执行必须维护与外部变量的绑定(k在本例中),它会如何变化:

var k;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  for (var i=0; i < 1000000; i++) { k = 'string' + 'string' + 'string' }
}
Run Code Online (Sandbox Code Playgroud)

好的,这里我们对执行时间有一些影响,但仍然相当快。浏览器知道 for 循环总是做同样的事情,但它只执行一次。例如,如果迭代确实创建了一个巨大的字符串怎么办?

var k;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  k = '';
  for (var i=0; i < 1000000; i++) { k += i.toString() }
}
Run Code Online (Sandbox Code Playgroud)

这使浏览器陷入了痛苦的境地,因为它必须返回这个数百万个字符的字符串。我们能让这更痛苦吗?

var k;

function update() {
  console.log(new Date() - lastTime);
  lastTime = new Date();
  k = '';
  for (var i=0; i < 1000000; i++) { k = ['hey', 'hey', 'hey'].join('') }
}
Run Code Online (Sandbox Code Playgroud)

这种数组串联无法优化,并且会缓慢而痛苦地阻塞几乎所有浏览器。

因此,在您的情况下,繁重的执行可能会导致优化器保留并立即释放更多内存。也许呼吸新鲜空气、额外的内存和空闲的 CPU 让你的函数高兴地跳跃,但正如我所说,如果不查看你的繁重的执行代码,就没有什么可靠的东西。