创建简单任务时遇到以下问题:使用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);
})();
任何想法为什么会发生这种情况以及如何解决这个问题?
答案 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
实现。
不幸的是,这意味着 setTimeout
或setInterval
的任何调用都会受到影响。
作为替代方案,您可以使用可爱的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);