为什么setTimeout不准时呢?

384 阅读2分钟

在电商行业,经常出现秒杀这个需求,说道秒杀就需要用倒计时组件,倒计时组件又需要用到定时器,常听大佬们说前端时间不准:好那今天就了解一下setTimeout()为什么不准时?

先打开MDN搜索setTimeout,看下官方的解释:该定时器在定时器到期后执行一个函数或指定的一段代码。

接下来研究定时器不准的原因有哪些?

嵌套的定时器存在最小延时 >=4ms

function cb() { f(); setTimeout(cb, 0); }
setTimeout(cb, 0);

setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,如果延迟时间小于 0,则会把延迟时间设置为 0。如果定时器嵌套 5 次以上并且延迟时间小于 4ms,则会把延迟时间设置为 4ms

未激活的页面,setTimeout的最小执行间隔是 1000ms

let num = 100;
function setTime() {
  // 当前秒执行的计时
  console.log(new Date().getSeconds() + ":" + num);
  num ? num-- && setTimeout(() => setTime(), 50) : "";
}
setTime();

为了优化后台 tab 的加载损耗(以及降低耗电量),在未被激活的 tab 中定时器的最小延时限制为 1S(1000ms)。当我在数字减到60的时候缩小浏览器窗口,此时会发现后台会已1s的规律去调用函数。

QQ截图20220716104912.jpg

当前任务执行太久

setTimeout函数会放在延迟队列中去执行,不会立即执行,需要等待前一个任务执行完后才能去执行,如果前一个任务耗时太久,会导致定时器设置的任务延迟执行,可以看面一个场景

setTimeout(() => {
  console.log(1);
}, 20);

setTimeout(() => {
  console.log(2);
}, 0);

上面这段显然非常简单,先输出2再输出1,但是如果中间加入一个同步任务

setTimeout(() => {
  console.log(1);
}, 20);
for (let i = 0; i < 90000000; i++) { } 
setTimeout(() => {
  console.log(2);
}, 0);

此时结果就不是我们想看到的了,会发现先输出1再输出2,这个场景说明回调任务是按顺序添加到延迟队列里面的,执行完同步任务后发现,定时器都已经过期了,就会按延迟队列添加的顺序依次执行,所以打印顺序是1和2。这里就会想了,setTimeout确实不准啊!

超出最大延迟时间

包括 IE、Chrome、Safari、Firefox 在内的浏览器其内部以 32 位带符号整数存储延时。这就会导致如果一个延时 (delay) 大于 2147483647 毫秒 (大约 24.8 天) 时就会溢出,导致定时器将会被立即执行。

总结

定时器的使用场景还有很多,但是对于时间精确度高的场景,就可以寻找替代品了,很多东西发现只有放在实际场景中才能看到好坏。