0.背景
0.1 业务需求:
需要在页面显示一个倒计时的计时器,后端返回截止时间,然后前端根据当前时间倒计时。此外在该页面还有上传文件,以及和用户之间的其他交互操作。
0.2 settimeout 和 setinterval 特点
1.定时器的延迟参数有最大时间:定时器第二个执行时间参数设置,不是无限大的,例如一年之后执行那肯定是不现实的,所以浏览器给参数设置了最小最大时间,最大时间为24.8天(即2147483647ms),如果超过这个时间就强制设置为0,且立即执行。
-- 设置最大时间不准确
2.最小时间间隔(嵌套使用定时器):这里的最小间隔时间不是延迟参数的最小时间,理论上参数时间可以设置为0,但是实际特定场景下,前后调用时间有一个间隔,即在浏览器中定时器的嵌套深度超过五层之后,前后调用的最小时间间隔是4ms。
-- 设置最小时间不准确
3.主线程执行任务阻塞执行时间导致不准确
-- 进入主线程后执行时间不准确
4.未被激活tab内,定时器调用的最小时间延迟-1000ms:浏览器为了降低点亮,节省损耗,会将其他tab页面的设置的定时器最小时间延迟拉长。这就导致:如果我设置一个页面定时器每隔50ms执行一次函数,当我离开这个页面后,浏览器会自动设置这个定时器变成最短1000ms执行一次。那么定时器执行的次数就无法保证,如果我对每秒钟内一定要执行20次,那么离开这个tab之后,肯定是不能满足我们每秒执行20次的要求的。
-- 进入主线程时间不准确
0.3 setInterval 相对于 setTimeout 缺点在哪里?
最大的问题在于他进入队列的判定机制:setinterval和settimeout最大的区别在于进入队列的时间:settimeout是时间到了直接放入队列,但是setinterval在放入队列之前会先做一次判断:判断任务队列中是否存在这个定时器任务的第二个实例。如果存在则不进入队列。
1.setInterval 规定时间内执行的次数不准确
前置条件:函数执行的时间110ms高于定时器设置的间隔时间50ms,第一个在执行的时候,函数执行了110ms,在这期间(第一个定时器执行到50ms时候)第二个定时器在50ms应该放入队列中,那么根据setinterval放入队列机制:发现第一个在主线程中,队列中没有其他任务实例,所以第二个定时器回调可以放入队列。而第一个定时器回调执行到100ms时候,第三个定时器应该放入队列,但是根据setinterval放入队列机制,发现队列中已经存在第二个了,那么不满足放入条件,这个时候就放弃放入第三个。直到150ms的时候,第二个进入执行线程,队列为空。第三个定时器满足进入条件,才进入队列。这样一看,在绝对时间的0ms(0ms看成第一个定时器进入队列时间点),50ms,100ms,150ms四个点应该都是定时器进入队列时间,但是在100ms时候定时器没有进入,因此在150ms内少了一个定时器,相应的执行次数也一定少一次。
2.setInterval 指定两次执行之间的间隔不准确,即可能会没有“间隔时间”的存在
举例一个场景:我们希望用户将点击弹窗关闭之后隔一段时间再次出现弹窗。如果这个用户隔了很久才点击关闭,那么根据setinterval放入队列机制:setInterval会在时间到了发现队列中没有相应的定时器任务实例在队列中,满足要求。因此将第二个函数放入队列中,那么用户刚刚点击关闭就会立即出现第二个弹窗,没有任何“间隔”的用户体验。所以如果注重“间隔”,那么慎用!
参考文献:www.jeffjade.com/2016/01/10/…
0.4 为什么 setTimeout 可以代替 setInterval
setTimeout可以代替setInterval,解决的问题是setInterval放入队列时间不准确的问题,因为不同的setTimeout放入队列是不会相互影响的,因此可以解决掉setInterval放入队列时间不准确的问题和间隔体验的问题,因为这里是fn执行完毕之后才会创建新的setTimeout,因此间隔问题也解决了。但是这个只能说是相对准确的解决方案,对于执行间隔次数问题依然存在。只是帮你在队列中应该放入的回调都放入进去,就不会存在队列中特定时间点(间隔时间)不会放入定时器实例在队列的问题,其他问题还是老样子。
总结:setTimeout 可以代替 setInterval可以解决两个问题:
(1)队列漏放的问题
(2)间隔体验的问题
const myInterval = (fn, time) => {
const timeout = () => {
setTimeout(() => {
fn();
timeout();
}, time);
};
timeout();
};
const test = () => {
console.log("111");
};
myInterval(test, 1000);
场景:定时器轮训发送请求,确认最新数据,如果网络状况不良,一个请求发出,还没有返回结果,它会坚持不懈的继续发送请求,最后导致的结果就是请求堆积。
1. 倒计时的组件实现方案
方案1: 如果使用初试时间,然后定时器每执行一次增加一秒,那么肯定是不准确的,因此定时器回调执行时间不准确 方案2: 使用截止时间同时new Date判断距离截止时间的差值,然后将这个差值换算成对应的时分秒,显示在屏幕上。这样相对准确些。但是存在两种场景问题: 1.如果用户修改本地时间,那么new Date的时间就是不准确的。解决方案:服务端在返回数据的时候带上截止时间和当前时间。但是会存在网络延迟的问题,因此我们还要计算出网络延迟的时间时间的一半(返回的时间),然后粗略算出倒计时。 2.如果存在一个主任务执行时间很长,那么就会导致队列中的“计算&换算截止时间差值”的回调函数一直等待执行,那么显示屏中的倒计时会在这阶段停止计时。
1.使用requestAnimationFrame为什么更准确? setTimeout,setInterval属于JS引擎,RAF属于GUI引擎
3.2 切换tab页面的影响