如何在javascript中创建一个准确的计时器?

2022-08-30 04:19:02

我需要创建一个简单但准确的计时器。

这是我的代码:

var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);

在正好3600秒后,它打印了大约3500秒。

  • 为什么它不准确?

  • 如何创建准确的计时器?


答案 1

为什么它不准确?

因为您正在使用 或 。它们不能被信任,它们没有准确性保证。它们被允许任意滞后,并且它们不会保持恒定的步伐,而是倾向于漂移(如您所观察到的)。setTimeout()setInterval()

如何创建准确的计时器?

请改用 Date 对象来获取(毫秒)准确的当前时间。然后,将逻辑基于当前时间值,而不是计算执行回调的频率。

对于简单的计时器或时钟,请显式跟踪时

var start = Date.now();
setInterval(function() {
    var delta = Date.now() - start; // milliseconds elapsed since start
    …
    output(Math.floor(delta / 1000)); // in seconds
    // alternatively just show wall clock time:
    output(new Date().toUTCString());
}, 1000); // update about every second

现在,这存在可能跳跃值的问题。当间隔滞后一点并在 、 、 、 毫秒后执行回调时,您将看到第二个计数 、 、 、 、 (!)。因此,建议更频繁地更新,例如大约每100毫秒更新一次,以避免此类跳跃。990199329963999500201235

但是,有时您确实需要一个稳定的间隔来执行回调而不会漂移。这需要更高级的策略(和代码),尽管它支付得很好(并且注册的超时更少)。这些被称为自调整计时器。在这里,与预期的间隔相比,每个重复超时的确切延迟都适应实际经过的时间:

var interval = 1000; // ms
var expected = Date.now() + interval;
setTimeout(step, interval);
function step() {
    var dt = Date.now() - expected; // the drift (positive for overshooting)
    if (dt > interval) {
        // something really bad happened. Maybe the browser (tab) was inactive?
        // possibly special handling to avoid futile "catch up" run
    }
    … // do what is to be done

    expected += interval;
    setTimeout(step, Math.max(0, interval - dt)); // take into account drift
}

答案 2

我只是在Bergi的答案(特别是第二部分)的基础上进行一点构建,因为我真的很喜欢它完成的方式,但是我希望可以选择在计时器启动后停止计时器(就像几乎一样)。所以。。。我已经把它包装成一个构造函数,这样我们就可以用它做“客观”的事情。clearInterval()

1. 构造函数

好吧,所以你复制/粘贴它...

/**
 * Self-adjusting interval to account for drifting
 * 
 * @param {function} workFunc  Callback containing the work to be done
 *                             for each interval
 * @param {int}      interval  Interval speed (in milliseconds)
 * @param {function} errorFunc (Optional) Callback to run if the drift
 *                             exceeds interval
 */
function AdjustingInterval(workFunc, interval, errorFunc) {
    var that = this;
    var expected, timeout;
    this.interval = interval;

    this.start = function() {
        expected = Date.now() + this.interval;
        timeout = setTimeout(step, this.interval);
    }

    this.stop = function() {
        clearTimeout(timeout);
    }

    function step() {
        var drift = Date.now() - expected;
        if (drift > that.interval) {
            // You could have some default stuff here too...
            if (errorFunc) errorFunc();
        }
        workFunc();
        expected += that.interval;
        timeout = setTimeout(step, Math.max(0, that.interval-drift));
    }
}

2. 实例化

告诉它该做什么等等...

// For testing purposes, we'll just increment
// this and send it out to the console.
var justSomeNumber = 0;

// Define the work to be done
var doWork = function() {
    console.log(++justSomeNumber);
};

// Define what to do if something goes wrong
var doError = function() {
    console.warn('The drift exceeded the interval.');
};

// (The third argument is optional)
var ticker = new AdjustingInterval(doWork, 1000, doError);

3. 然后...东西

// You can start or stop your timer at will
ticker.start();
ticker.stop();

// You can also change the interval while it's in progress
ticker.interval = 99;

我的意思是,无论如何,它对我有用。如果有更好的方法,莱姆知道。