还有比 setTimeout 更精确的创建 Javascript 计时器的方法吗?

一直困扰我的是 Javascript 中 setTimeout()方法的不可预测性。

根据我的经验,计时器在很多情况下都是非常不准确的。不准确,我的意思是实际延迟时间似乎差异250-500毫秒或更少。虽然这并不是一个巨大的时间量,但是当使用它来隐藏/显示 UI 元素时,时间是可见的。

是否有任何技巧可以做到,以确保 setTimeout()执行正确(不诉诸外部 API)或这是一个失败的原因?

42244 次浏览

我不想这么说,但我觉得没有办法缓解这种情况。不过,我确实认为这取决于客户端系统,因此一个更快的 javascript 引擎或机器可能会使它更准确地使用 有点

根据我的经验,这是失去的努力,即使是最小的合理数额的时间,我认为在约32-33毫秒的行动..。

这里肯定有一个限制。Google 刚刚发布的 Chrome 浏览器速度足够快,可以在15-20毫秒内执行 setTimeout (function (){} ,0) ,而旧的 Javascript 引擎执行这个函数需要几百毫秒。虽然 setTimeout 使用毫秒,但此时没有任何 javascript 虚拟机能够以这种精度执行代码。

Dan,根据我的经验(包括在 JavaScript 中实现 SMIL2.1语言,其中时间管理是主题) ,我可以向您保证,您实际上永远不需要 setTimeout 或 setInterval 的高精度。

然而,排队时 setTimeout/setInterval 的执行顺序才是最重要的——而且这个顺序总是很完美。

有没有什么诀窍 确保 setTimeout()的表现 准确地(不诉诸于 外部 API)或这是一个失败的原因?

不是,也不是。你不会得到任何接近一个完全准确的定时器与 setTimeout()-浏览器没有为此设置。然而,你也不需要依赖它来计时。大多数动画库在几年前就已经发现了这一点: 您使用 setTimeout()设置了一个回调函数,但是根据 (new Date()).milliseconds的值(或等效值)确定需要执行的操作。这使您能够在较新的浏览器中利用更可靠的计时器支持,同时在较旧的浏览器上仍能正常运行。

它还允许你 避免使用太多计时器!这一点很重要: 每个计时器都是一个回调。每个回调执行 JS 代码。在执行 JS 代码时,浏览器事件(包括其他回调)会被延迟或删除。当回调结束时,其他回调必须与其他浏览器事件竞争才有机会执行。因此,一个计时器处理该时间间隔内所有挂起的任务将比两个具有重合时间间隔的计时器执行得更好,并且(对于短时间超时)比两个具有重叠时间间隔的计时器执行得更好!

摘要: 停止使用 setTimeout()来实现“一个计时器/一个任务”的设计,使用实时时钟来平滑 UI 操作。

JavaScript 超时事实上的限制是10-15ms (我不知道您是如何获得200ms 的,除非您实际执行了185ms 的 js)。这是由于 Windows 的标准定时器分辨率为15ms,唯一能做得更好的方法是使用 Windows 的高分辨率定时器,这是一个系统范围的设置,因此可以在系统上的其他应用程序,也咀嚼电池寿命(Chrome 有一个错误,从英特尔在这个问题)。

事实上,10-15ms 的标准是因为人们在网站上使用0ms 超时,但是编码的方式却假设了10-15ms 的超时(比如 js 游戏,假设 fps 为60,但是要求0ms/frame,没有增量逻辑,所以游戏/网站/动画的数量级比预期的要快一些)。为了解决这个问题,即使在没有 Windows 定时器问题的平台上,浏览器也将定时器分辨率限制在10毫秒。

Shog9的回答和我想说的差不多,尽管我想添加以下关于 UI 动画/事件的内容:

如果你有一个应该滑动到屏幕上的方框,向下展开,然后在其内容中淡出,不要试图让所有三个事件分开,延迟时间使它们一个接一个地触发-使用回调,所以一旦第一个事件完成滑动,它调用扩展器,一旦完成它调用淡出器。JQuery 可以很容易地做到这一点,我相信其他库也可以做到这一点。

你需要在目标时间上“爬行”。一些尝试和错误将是必要的,但在本质上。

设置一个超时,在规定的时间前约100毫秒完成

让超时处理程序的功能如下:

calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
re-queue the handler for 10ms time
else
{
while( remaining_time > 0 ) calculate_remaining_time;
do_your_thing();
re-queue the handler for 100ms before the next required time
}

但是 while 循环仍然可能被其他进程中断,所以它仍然不完美。

如果你需要在给定的时间间隔内得到一个准确的回调,这个要点可以帮助你:

Https://gist.github.com/1185904

function interval(duration, fn){
var _this = this
this.baseline = undefined
  

this.run = function(){
if(_this.baseline === undefined){
_this.baseline = new Date().getTime()
}
fn()
var end = new Date().getTime()
_this.baseline += duration
 

var nextTick = duration - (end - _this.baseline)
if(nextTick<0){
nextTick = 0
}
    

_this.timer = setTimeout(function(){
_this.run(end)
}, nextTick)
}


this.stop = function(){
clearTimeout(_this.timer)
}
}

.

http://www.sitepoint.com/creating-accurate-timers-in-javascript/

这个网站大规模地保释了我。

您可以使用系统时钟来补偿计时器的不准确性。如果你以一系列 setTimeout 调用(每个实例调用下一个)的方式运行一个计时函数,那么你要做的就是精确地计算出它到底有多不准确,然后从下一次迭代中减去这个差值:

var start = new Date().getTime(),
time = 0,
elapsed = '0.0';
function instance()
{
time += 100;
elapsed = Math.floor(time / 100) / 10;
if(Math.round(elapsed) == elapsed) { elapsed += '.0'; }
document.title = elapsed;
var diff = (new Date().getTime() - start) - time;
window.setTimeout(instance, (100 - diff));
}
window.setTimeout(instance, 100);

这种方法可以最大限度地减小误差,使误差降低90% 以上。

解决了我的问题,希望能有帮助

这里有一个示例演示 Shog9的建议。这将在6秒内平稳地填充一个 jquery 进度条,然后在填充后重定向到另一个页面:

var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();


setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);


function updateProgress() {
var currentTime = new Date().getTime();


// 1000 to convert to milliseconds, and 100 to convert to percentage
percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;


$("#progressbar").progressbar({ value: percent });


if (percent >= 100) {
window.location = "newLocation.html";
} else {
setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
}
}

不久前,我遇到过类似的问题,并且提出了一种将 requestAnimationFrame和 Performance. now ()结合起来的方法,这种方法非常有效。

我现在能够使计时器精确到小数点后12位左右:

    window.performance = window.performance || {};
performance.now = (function() {
return performance.now       ||
performance.mozNow    ||
performance.msNow     ||
performance.oNow      ||
performance.webkitNow ||
function() {
//Doh! Crap browser!
return new Date().getTime();
};
})();

Http://jsfiddle.net/cgwgreen/9pg9l/

如果你正在使用 setTimeout()来快速生成浏览器 ,这样它的 UI 线程就可以完成它需要完成的任何任务(比如更新选项卡,或者不显示长运行脚本对话框) ,有一个新的 API 称为 高效的脚本生成,也就是 setImmediate(),它可能对你更有用一些。

setImmediate()的运行方式与 setTimeout()非常相似,但如果浏览器没有其他事情可做,它可以立即运行。在许多使用 setTimeout(..., 16)setTimeout(..., 4)或者 setTimeout(..., 0)的情况下(例如,你希望浏览器运行任何未完成的 UI 线程任务,而不显示长运行脚本对话框) ,你可以简单地用 setImmediate()代替你的 setTimeout(),去掉第二个(毫秒)参数。

setImmediate()的不同之处在于它基本上是一个产量; 如果浏览器有时间在 UI 线程上做(例如,更新选项卡) ,它会在返回回调之前这样做。但是,如果浏览器已经完成了所有工作,那么 setImmediate()中指定的回调基本上将立即运行。

不幸的是,目前只有 IE9 + 支持,因为其他浏览器厂商也有一些 反击

但是,如果您希望使用它并希望其他浏览器在某个时候实现它,那么还有一个 填充物很好可用。

如果使用 setTimeout()处理动画 ,那么 RequestAnimationFrame是最佳选择,因为代码将与监视器的刷新率同步运行。

如果你在一个较慢的节奏上使用 setTimeout(),例如每300毫秒使用一次,你可以使用一个类似 user1213320建议的解决方案,你可以监视它从你的计时器上次运行的时间戳到现在有多长时间,并补偿任何延迟。一个改进是您可以使用新的 高分辨率时间接口(又名 window.performance.now())而不是 Date.now()来获得当前时间的大于毫秒的分辨率。

这是我为我的一个音乐项目做的定时器。对所有设备都精确的计时器。

var Timer = function(){
var framebuffer = 0,
var msSinceInitialized = 0,
var timer = this;


var timeAtLastInterval = new Date().getTime();


setInterval(function(){
var frametime = new Date().getTime();
var timeElapsed = frametime - timeAtLastInterval;
msSinceInitialized += timeElapsed;
timeAtLastInterval = frametime;
},1);


this.setInterval = function(callback,timeout,arguments) {
var timeStarted = msSinceInitialized;
var interval = setInterval(function(){
var totaltimepassed = msSinceInitialized - timeStarted;
if (totaltimepassed >= timeout) {
callback(arguments);
timeStarted = msSinceInitialized;
}
},1);


return interval;
}
}


var timer = new Timer();
timer.setInterval(function(){console.log("This timer will not drift."),1000}

你可以考虑使用 Html5网络音频时钟,它利用系统时间来获得更好的准确性

下面是我使用的代码,因为它是 JavaScript,所以我将同时发布我的 Frontendandnode.js 解决方案:

对于这两种情况,我都使用同样的十进制舍入函数,我强烈建议你保持一臂的距离,因为:

const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)

places-四舍五入的小数位数,这应该是安全的,应该避免任何问题与浮动(一些数字,如1.00000000000005 ~ 可能是有问题的)。我花时间研究了由转换为毫秒的高分辨率计时器提供的舍入小数的最佳方法。

它是一个一元运算符,将操作数转换成数字,实际上与 Number()相同

浏览器

const start = performance.now()


// I wonder how long this comment takes to parse


const end = performance.now()


const result = (end - start) + ' ms'


const adjusted = round(2, result) // see above rounding function

Node Js

// Start timer
const startTimer = () => process.hrtime()


// End timer
const endTimer = (time) => {
const diff = process.hrtime(time)
const NS_PER_SEC = 1e9
const result = (diff[0] * NS_PER_SEC + diff[1])
const elapsed = Math.round((result * 0.0000010))
return elapsed
}


// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.


const start = startTimer()


// I wonder how long this comment takes to parse


const end = endTimer(start)


console.log(end + ' ms')