WebKit setInterval和系统时间更改

时间:2014-04-11 13:01:15

标签: javascript webkit

创建简单任务时遇到以下问题:使用WebKit引擎显示html时钟。额外的要求是处理系统时间变化,它应该在Windows上工作。 我已经使用setInterval来实现这一点,但是在我向后改变系统时间之后它似乎会冻结浏览器。 对我来说,它看起来像WebKit问题。通过运行这个简单的代码很容易在safari上重现:

<p id="date"></p>
setInterval(SetTime, 1000);
function SetTime() {
document.getElementById('date').textContent=new Date();
}

之后我用递归的setTimeout调用做了另一种方法。同样的效果。

(function loop() {
document.getElementById('date').textContent=new Date();
setTimeout(loop, 1000);
})();

任何想法为什么会发生这种情况以及如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

这几乎肯定是WebKit的一个问题。

问题

使用setTimeout时,您可以创建一个包含两个属性的“计时器”:

  • 时间戳,设置为现在+指定的延迟
  • 一旦系统时间大于时间戳,就会触发一次回调

你可以想象setTimeout的天真实现看起来像这样:

var timers = [];
function setTimeout(callback, delay) {
    var id = timers.length;
    timers[id] = {
        callback: callback,
        timestamp: Date.now() + delay
    }
    return id;
}

这只会创建一个计时器并将其添加到列表中。然后,在每个tick上,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实现。

不幸的是,这意味着 setTimeoutsetInterval的任何调用都会受到影响。

解决方案

作为替代方案,您可以使用可爱的window.requestAnimationFrame方法在每个tick上执行回调。我根本没有对此进行测试,但无论系统时间如何,它都应该继续在每个刻度上触发。

作为奖励,每次触发回调时,您都会将当前时间戳作为参数传递。通过跟踪传递给回调的先前时间戳,您可以轻松检测到向后的系统时间变化:

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);