注册
web

setTimeout是准时的吗?

88e83e0567db4a4bb1506fa86b31c21f.jpeg


引言


最近在一些论坛上,有人讨论 setTimeout 的准确性。因此,我进行了探索,以解答这个问题。结果发现,setTimeout 并不完全可靠,因为它是一个宏任务。所指定的时间实际上是将任务放入主线程队列的时间,而不是任务实际执行的时间。


`setTimeout(callback, 进入主线程的时间)`

因此,何时执行回调取决于主线程上待处理的任务数量。


演示


这段代码使用一个计数器来记录每次 setTimeout 的调用。设定的间隔时间乘以计数次数,理想情况下应等于预期的延迟。通过以下示例,可以检查我们计时器的准确性。


function time () {
var speed = 50, // 间隔
count = 1 // 计数
start = new Date().getTime();

function instance() {
var ideal=(count * speed),
real = (new Date().getTime() - start);

count++;
console.log(count + '理想值------------------------:', ideal); // 记录理想值
console.log(count + '真实值------------------------:', real); // 记录理想值

var diff = (real - ideal);
console.log(count + '差值------------------------:', diff); // 差值

// 小于5执行
if (count < 5) {
window.setTimeout(function(){
instance();
}, speed);
};

};

window.setTimeout(function () {
instance();
}, speed);
};

打印1


image.png


我们可以在 setTimeout 执行之前加入额外的代码逻辑,然后再观察这个差值。


...
window.setTimeout(function(){
instance();
}, speed);
for(var a = 1, i = 0; i < 10000000; i++) {
a *= (i + 1);
};
...

打印2


image.png


可以看出,这大大增加了误差。随着时间的推移,setTimeout 实际执行的时间与理想时间之间的差距会不断扩大,这并不是我们所期望的结果。在实际应用中,例如倒计时和动画,这种时间偏差会导致不理想的效果。


image.png


如何实现更精准的 setTimeout


requestAnimationFrame


window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。


该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒60次,也就是每16.7ms 执行一次,但是并不一定保证为 16.7 ms。


我们用requestAnimationFrame模拟 setTimeout


function setTimeout2(cb, delay) {
const startTime = Date.now();

function loop() {
const now = Date.now();
if (now - startTime >= delay) {
cb();
} else {
requestAnimationFrame(loop);
}
}

requestAnimationFrame(loop);
};

打印3


image.png


貌似误差问题还是没有得到解决,因此这个方案还是不行。


while


想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用while可以实现这个功能。


function time2(time) {
const startTime = Date.now();

function checkTime() {
const now = Date.now();
if (now - startTime >= time) {
console.log('误差', now - startTime - time);
} else {
setTimeout(checkTime, 1); // 每毫秒检查一次
}
}

checkTime();
}

time2(5000);

误差存在是 2, 甚至为 0, 但使用 while(true) 会导致 CPU 占用率极高,因为它会持续循环而不进行任何等待,会使得页面进入卡死状态,这样的结果显然是不合适的。


setTimeout 系统时间补偿


这个方案是在 Stack Overflow 看到的一个方案,我们来看看此方案和原方案的区别。


当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。


function time () {
var speed = 50, // 间隔
count = 1 // 计数
start = new Date().getTime();

function instance() {
var ideal=(count * speed),
real = (new Date().getTime() - start);

count++;
console.log(count + '理想值------------------------:', ideal); // 记录理想值
console.log(count + '真实值------------------------:', real); // 记录理想值

var diff = (real - ideal);
console.log(count + '差值------------------------:', diff); // 差值

// 5次后不再执行
if (count < 5) {
window.setTimeout(function(){
instance();
}, (speed - diff));
};
};

window.setTimeout(function () {
instance();
}, speed);
};

打印4


image.png


image.png


结论


多次尝试后,是非常稳定的,误差微乎其微,几乎可以忽略不计,因此通过系统的时间补偿,能使 setTimeout 变得更加准时。


作者:半壳椰子
来源:juejin.cn/post/7420059840971980834

0 个评论

要回复文章请先登录注册