JavaScript各种定时器总结

2,445 阅读5分钟

setTimeout与setInterval

setTimeout和setInterval已经存在已久了,我们什么场景下会使用它们完成我们的业务呢?我列举一些例子:

  • 轮询接口
  • 动画
  • 不知名的bug去解决

我们会遇到使用它们的场景其实归纳起来就是以上三点。

轮询接口这种情景,一般出现在不用socket的情况下使用的,例如我们有一个支付功能,前端调用了sdk获取h5支付链接后,页面就需要轮询一个后端的接口去查询这个订单的支付结果。这个时候有人会说为什么不用socket呢?原因有很多,一部分原因是以前项目就是这样,另一方面,可能服务器对于一个用户支付开启一个socket对于服务器的性能是有负担的,所以不去开启。要求前端轮询,当然有人也会说为什么不直接一个请求,后端内部sleep这个请求直到有支付结果呢?sleep了这一个请求不就阻塞了一个进程吗?所以用这么low的方式请求是有原因的。好了,言归正传,既然轮询,前端就需要使用setTimeout了。而且一般来说,轮询不能无休止的轮询,中间必须要有停顿。不然你和刷接口有什么分别呢?这个时候就可以选择setTimeout或者setInterval了。

制作动画时少不了使用setTimeout和setInterval,特别是一个经典的情景,就是一个元素本来的display是none的。需要先将其display改为非none的值,然后插入class实现动画执行。这个时候如果你的元素直接加入一个class,这个class同一时间将display设置成block,然后同时启动动画。那么就会出现元素是显示了,但是动画缺没有出来。这个时候一般解决方式都是先将元素的display改为block,然后在setTimeout里面加入为元素添加class的代码,并且延时0毫秒。我相信不少人会这么做,这个也是比较方便和常规的做法。

不知名的bug用setTimeout就能解决了。这个就很经典了,本人在公司内,曾经用一个3层嵌套的setTimeout完成一个功能。(当然当时比较紧急)。但是确实这个事情经常能遇到,往往这是得益于setTimeout的执行机制问题,无端端帮我们解决了一些调用顺序导致的bug。所有才会产生一句经典的话《没什么bug是一个setTimeout解决不了的,如果有就再加一个setTimeout》。

我们分别说一说setTimeout和setInterval的原理以及区别

setTimeout

![](https://pic4.zhimg.com/80/v2-ae797535e61a1d80e0314a4df506b3ee_720w.jpg)

setInterval

![](https://pic3.zhimg.com/80/v2-adbf7a3859c157cdf3a668e613a47547_720w.jpg)

setTimeout和setInterval的执行原理其实差不多,关键是在于两个定时器对于回调函数的执行时机的问题,setTimeout是将回调函数推入任务队列,并且在每一次执行任务队列的时候判断这个定时任务时候到时间执行了,图中假定是300毫秒,如果执行到定时任务的时候发现距离推入任务队列的时间以及 >= 300了,那么将执行,否则放到下一次的任务队列中。

但是setInterval就完全不一样了,定时时间是规定多久将回调函数推进任务队列中,然后每一次执行任务队列的时候都执行定时器的回调任务。

为什么不要使用setInterval?

很简单的一例子,如果使用setInterval,里面的回调函数中,需要执行比较长的事件,例如setInterval一个1秒钟的时间,然后callback中需要执行3秒,那么下一个setInterval并不会等待上一个的setInterval的callback执行完毕才执行,这样就有可能出现同一时间触发多次setInterval的callback,然后导致页面的奇怪现象。另外这样也容易造成内存溢出。

建议使用setTimeout代替setInterval,在setTimeout的callback中,知心完后重新新建一个setTimeout。这样就保证了每一次只会有一个定时任务执行。

requestAnimationFrame

在日常使用setTimeout中,我们最常用的就是用来制作动画(操作dom)和定时访问接口。当然requestAnimationFrame不适合用来定时访问接口,除非你的接口要你16毫秒请求一次吧。

其实我们经常用setTimeout将元素的display改为block,然后添加动画class也是会有bug的,例如我们定时任务修改完了元素的display属性,但是浏览器并没有刷新整个dom的结构,你就添加了class也会有可能造成动画失效的情况。这个时候就是使用requestAnimationFrame的时候,

首先requestAnimationFrame是不需要传入时间的,他的触发时机是根据当前设备的屏幕刷新率来的,例如:如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms。requestAnimationFrame始终严格按照设备的屏幕刷新频率来设定触发时机的。

除此之外,requestAnimationFrame还有以下两个优势:

  1. 对CPU友好,如果使用setTimeout实现动画,那么页面如果不处于激活的状态,其实setTimeout还是会继续在后台执行的,但是requestAnimationFrame就不一样了,当页面不是激活状态的情况下,那么该页面的屏幕刷新也会停止,从而requestAnimationFrame也会停止执行。当页面激活的时候,动画就从上一次的停止的地方继续执行,非常有效的节省了CPU的开销。
  2. 函数节流:因为requestAnimationFrame是固定以屏幕的刷新频率去触发的,所以不会存在在高频事件中重复触发函数的情况。

备注:requestAnimationFrame是运行在主线程上的,所以如果主线程执行耗时很长的任务的话,会对requestAnimationFrame造成影响。

总结

setTimeout,setInterval和requestAnimationFrame其实并不是同一样东西,但是requestAnimationFrame的出现就是为了代替setTimeout去制作动画的。而且也尽可能不要使用setInterval,如果必须要用setInterval,也一定要加上clearInterval去清楚。否则很容易出现一些不可预料的情况。