最近,我在做一些有关setInterval方法的尝试.对于初学者来说,setInterval是可以让你在指定的时间间隔里重复执行某段代码的方法。
比如,你可以用下面的代码创建一个每秒执行的时间间隔
比如,你可以用下面的代码创建一个每秒执行的时间间隔
setInterval(function() {
console.log('I execute every second!');
}, 1000);
受JavaScript单线程机制的影响,setInterval和它的表哥setTimeout都是会延迟执行的。当你打算创建1000毫秒执行一次的时间间隔,实际上可能需要超过1000毫秒才能触发一次。通常只会有几毫秒的延迟,看起来似乎是一个微不足道的问题。但也许你会希望能够在之后的循环里能够努力纠正这个时间误差,把这个时间间隔控制到预期的效果。换句话说,如果代码执行时间(假定延迟7毫秒)和设定的时间间隔一共用了1007毫秒,你会尽量将下一个执行发生的时间安排在接近2000毫秒(即993毫秒后)。
但由于浏览器实现的差异,事实上执行时间可能并不是你预期的那样。我们可以通过跟踪setInterval里的时间戳来看看。
如果你在Chrome,Safari,Internet Explorer,或Node.js的当前版本运行这段代码,你会发现时间间隔会随着执行次数的增加同步增加。
var startTime = Date.now();
setInterval(function() {
console.log((Date.now() - startTime) + 'ms elapsed');
}, 1000);
在我的测试,我发现只有Firefox试图保持间隔执行同步。
不管这是否是setInterval预期的行为,我需要这个时间间隔尽可能的按设定的时间间隔来执行。下面是我对这个问题的解决方案:
上面的代码将在window宿主下创建一个setCorrectingInterval全局函数,它的形参是和setInterval相同的
下面我们列一下在这个方法里我们到底做了什么:
1.在闭包环境里,包装了一个tick方法,用以跟踪时间间隔对象的实例
2.当方法被第一次调用时(相当于instance.started为False),在instance对象里初始化一些必须的属性
3. 因setInterval的执行间隔本身是不可控的(译者注),我们使用setTimeout来代替。配合tick及调节过的延迟时间来循环调用。
4.通过计算上次函数被调用的时间与当前时间的间隔加以计算,预期下次的延迟。
我们可以使用setcorrectinginterval来代替使用setInterval来达到我们的目地。
不管这是否是setInterval预期的行为,我需要这个时间间隔尽可能的按设定的时间间隔来执行。下面是我对这个问题的解决方案:
window.setCorrectingInterval = (function(func, delay) {
var instance = { };
function tick(func, delay) {
if (!instance.started) {
instance.func = func;
instance.delay = delay;
instance.startTime = new Date().valueOf();
instance.target = delay;
instance.started = true;
setTimeout(tick, delay);
} else {
var elapsed = new Date().valueOf() - instance.startTime,
adjust = instance.target - elapsed;
instance.func();
instance.target += instance.delay;
setTimeout(tick, instance.delay + adjust);
}
};
return tick(func, delay);
});
上面的代码将在window宿主下创建一个setCorrectingInterval全局函数,它的形参是和setInterval相同的
下面我们列一下在这个方法里我们到底做了什么:
1.在闭包环境里,包装了一个tick方法,用以跟踪时间间隔对象的实例
2.当方法被第一次调用时(相当于instance.started为False),在instance对象里初始化一些必须的属性
3. 因setInterval的执行间隔本身是不可控的(译者注),我们使用setTimeout来代替。配合tick及调节过的延迟时间来循环调用。
4.通过计算上次函数被调用的时间与当前时间的间隔加以计算,预期下次的延迟。
我们可以使用setcorrectinginterval来代替使用setInterval来达到我们的目地。
var startTime = Date.now();
setCorrectingInterval(function() {
console.log((Date.now() - startTime) + 'ms elapsed');
}, 1000);
下图中可以看出,执行的时间间隔并没有不断增加,而是尽可能的将代码控制在每秒执行一次。
译者注:
写这个文章的人一定很但蛋疼,这篇博文的信息量,个人感觉不如它的评论大。评论中提到了CPU优先级及性能问题,可以看看。
The main problem with this solution is that setInterval has a higher CPU priority than setTimeout or requestAnimationFrame. So the browser will prioritize these calls in the event loop and it _MAY_ cause performance issues in other areas of the site depending on what you're doing. That said, if you have a site that just displays a clock I'm sure you're fine :)
一个老兄的评论:
这种方法的主要问题是,
setInterval比
setTimeout及requestanimationframe较高的CPU优先级。所以浏览器在处理调用的事件循环时,可能会引起性能问题。
邮箱:simon4545##126.com
原文链接:http://www.andrewduthie.com/post/a-self-correcting-setinterval-alternative/?utm_source=javascriptweekly&utm_medium=email
这是我第一次翻译外文,水平有限,我连CET-4都没有过,如果有不对之处,欢迎指正