了解更多开源请访问:51CTO开源基础软件社区https://ost.51cto.com前言前言定时器项目中遇到函数。项目中的定时器其实只是一个显示功能,所有的数据都是由设备上报的。做完项目后,自己做了一个定时器小组件,过程中发现了一些问题。效果展示组件直接秒传数据,最终展示如下效果:实现原理1.使用setTimeout模拟setInterval的行为一般情况下,说到定时器,首先想到的是使用设置间隔。与setTimeout相比,需要反复调用。setInterval可以很方便的实现,如下代码:getTime(time){this.countNum=time;setTimeout(()=>{this.getTime(time--)},1000)},setInterval(()=>{this.countNum--},1000)但是为什么要用setTimeout呢?查看两个定时器的原理后,会发现创建一个时间间隔为100ms的定时器,setInterval每100ms向队列中添加一个事件;100ms后,将T1定时器代码加入队列,主线程中还有任务正在执行,所以等待,等某个事件执行完再执行T1定时器代码;又过了100ms,T2定时器加入队列,主线程还在执行T1代码,所以在等待;又过了100ms,理论上需要往队列中压入一个定时器代码,但是由于此时T2还在队列中,所以不会加入T3,结果此时被跳过了;这里我们可以看到T2的代码是在T1定时器执行完后立即执行的,所以并没有达到定时器的效果。综上所述,setInterval有两个缺点:使用setInterval时,会跳过一些间隔。可能有多个定时器连续执行。因此,我们需要用setTimeout来模拟setInterval来避免上述缺点。2、使用Date.now()获取当前时间,避免浏览器退出再进入导致的计时错误。我们在使用计时工具的时候,由于某些原因,浏览器退到了后台,等我们再次进来的时候,发现计时器的时间不对。感觉在刚才退出的这段时间里,计时器停止了。针对这个问题,查询会发现,有些浏览器为了节能,在进入后台(或者失去焦点时)的时候,会暂停setTimeout等定时任务,当定时任务会重新激活用户返回到浏览器。.说是暂停,但是实践操作会发现应该说是延迟。1s的任务延迟到2s,或者更长。简而言之,定时器计算的时间小于实际经过的时间。为了解决这个问题,我们可以使用Date.now()来记录时间,如下,通过计算两个计时事件的时间差来计算每次计时的步长。getTime(时间){this.countNum=时间;setTimeout(()=>{constnowDate=Date.now()constdiff=Math.floor((nowDate-this.curTime)/1000)conststep=diff>1?diff:1//有偏差页面返回后台后计时,比较时间差得到计时步长this.curTime=nowDatethis.getTime(time-step)},1000)},3.定时器及时清理很容易忽略有一点,我们组件的唯一prop属性是时间。在实际的业务场景中,可能会有一些改变倒计时时长的操作,所以我们的组件需要监听时间值的变化来做一些初始化操作。这时候你会发现经过2次初始化操作后,我们的代码中会同时出现2个定时器。1秒后,2个定时器会同时被触发,两次时间值都为“-1”,所以,我们需要在初始化的时候把之前的定时器清零。getTime(time){this.timer&&clearTimeout(this.timer)//清除定时器this.countNum=time;this.timer=setTimeout(()=>{constnowDate=Date.now()constdiff=Math.floor((nowDate-this.curTime)/1000)conststep=diff>1?diff:1//有页面返回后台后计时出现偏差,比较时间差得到计时步长this.curTime=nowDatethis.getTime(time-step)},1000)},实现流程countDown组件hml部分:
