这几乎肯定是 WebKit 的一个问题。
问题
当您使用setTimeout 时,您会创建一个具有两个属性的“计时器”:
- 一个时间戳,设置为现在+指定的延迟
-
回调在系统时间大于时间戳时触发一次。
你可以想象一个简单的 setTimeout 实现看起来像这样:
var timers = [];
function setTimeout(callback, delay) {
var id = timers.length;
timers[id] = {
callback: callback,
timestamp: Date.now() + delay
}
return id;
}
这将简单地创建一个计时器并将其添加到列表中。然后,在每个滴答声中,JS 运行时都会检查这些计时器并为那些已触发的计时器执行回调:
var now = Date.now();
for(var id in timers) {
var timer = timers[id];
if(timer && timer.timestamp < now) {
callback();
delete timers[id];
}
}
鉴于此实现,想象现在将系统时间(即上述示例中的 Date.now())更改为过去的值 - 计时器的时间戳仍将相对于之前的系统时间(即未来)设置.
setInterval 也存在同样的问题,它(假设 WebKit 中的代码正常)将使用 setTimeout 实现。
不幸的是,这意味着任何对setTimeout 或setInterval 的调用都会受到影响。
解决方案
作为替代方案,您可以使用可爱的window.requestAnimationFrame 方法对每个报价执行回调。我根本没有对此进行测试,但无论系统时间如何,它都应该在每次滴答时继续触发。
作为奖励,每次它触发回调时,都会将当前时间戳作为参数传递。通过跟踪传递给回调的前一个时间戳,您可以轻松地检测到向后的系统时间变化:
var lastTimestamp;
var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
var checkForPast = function(timestamp) {
if(lastTimestamp && timestamp < lastTimestamp) {
console.error('System time moved into the past! I should probably handle this');
}
lastTimestamp = timestamp;
onNextFrame(checkForPast);
};
onNextFrame(checkForPast);
结论
这对您来说可能不是好消息,但您可能应该重写整个应用程序以使用 requestAnimationFrame - 它似乎更适合您的需求:
var onNextFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
var dateElem = document.getElementById('date');
var updateDate = function(timestamp) {
dateElem.textContent = new Date();
onNextFrame(updateDate);
};
onNextFrame(updateDate);