setTimeout是准时的吗?
引言
最近在一些论坛上,有人讨论 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
我们可以在 setTimeout
执行之前加入额外的代码逻辑,然后再观察这个差值。
...
window.setTimeout(function(){
instance();
}, speed);
for(var a = 1, i = 0; i < 10000000; i++) {
a *= (i + 1);
};
...
打印2
可以看出,这大大增加了误差。随着时间的推移,setTimeout
实际执行的时间与理想时间之间的差距会不断扩大,这并不是我们所期望的结果。在实际应用中,例如倒计时和动画,这种时间偏差会导致不理想的效果。
如何实现更精准的 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
貌似误差问题还是没有得到解决,因此这个方案还是不行。
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
结论
多次尝试后,是非常稳定的,误差微乎其微,几乎可以忽略不计,因此通过系统的时间补偿,能使 setTimeout
变得更加准时。
来源:juejin.cn/post/7420059840971980834