[译]可以自纠正的setInterval代替方法

最近,我在做一些有关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 = (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 :)
一个老兄的评论:
这种方法的主要问题是, setIntervalsetTimeout及requestanimationframe较高的CPU优先级。所以浏览器在处理调用的事件循环时,可能会引起性能问题。

邮箱:simon4545##126.com
原文链接:http://www.andrewduthie.com/post/a-self-correcting-setinterval-alternative/?utm_source=javascriptweekly&utm_medium=email
这是我第一次翻译外文,水平有限,我连CET-4都没有过,如果有不对之处,欢迎指正
JavaScript 中,我们可以使用 `setInterval()` 方法来实现定时器功能。但是,使用 `setInterval()` 方法存在一些问题,比如: - 可能会出现任务重叠的情况,导致任务执行时间延迟。 - 可能会因为浏览器或电脑性能的问题,导致定时器的间隔时间不准确。 为了解决这些问题,我们可以使用一些替代方案来实现定时器功能。 1. 使用 `setTimeout()` 递归调用 我们可以使用 `setTimeout()` 方法来实现递归调用,从而达到定时器的效果。在每次递归调用时,可以根据具体的情况来调整下一次调用的时间间隔。 ```javascript function mySetInterval(fn, interval) { setTimeout(function() { fn(); mySetInterval(fn, interval); }, interval); } // 使用示例 mySetInterval(function() { console.log('Hello world!'); }, 1000); ``` 2. 使用 `requestAnimationFrame()` 方法 `requestAnimationFrame()` 方法是一个专门用来实现动画效果的方法,它可以在每次浏览器重绘之前执行一段代码。因此,我们可以利用这个方法来实现定时器的效果。 ```javascript function mySetInterval(fn, interval) { let start = Date.now(); function loop() { let now = Date.now(); if (now - start >= interval) { fn(); start = now; } requestAnimationFrame(loop); } requestAnimationFrame(loop); } // 使用示例 mySetInterval(function() { console.log('Hello world!'); }, 1000); ``` 3. 使用 `MessageChannel` 实现 `MessageChannel` 是 HTML5 新增的一个 API,可以用来创建一个双向通道,可以在两个窗口之间传递消息。我们可以利用它的特性来实现定时器的效果。 ```javascript function mySetInterval(fn, interval) { let channel = new MessageChannel(); let timer = null; channel.port2.onmessage = function() { fn(); timer = setTimeout(function() { channel.port1.postMessage(null); }, interval); }; timer = setTimeout(function() { channel.port1.postMessage(null); }, interval); } // 使用示例 mySetInterval(function() { console.log('Hello world!'); }, 1000); ``` 以上三种方法都可以替代 `setInterval()` 方法来实现定时器的效果,但是它们各自的实现方式有所不同,具体使用时需要根据实际情况选择合适的方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值