在电商行业,经常出现秒杀这个需求,说道秒杀就需要用倒计时组件,倒计时组件又需要用到定时器,常听大佬们说前端时间不准:好那今天就了解一下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的规律去调用函数。
当前任务执行太久
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 天) 时就会溢出,导致定时器将会被立即执行。
总结
定时器的使用场景还有很多,但是对于时间精确度高的场景,就可以寻找替代品了,很多东西发现只有放在实际场景中才能看到好坏。