前一篇文章聊单线程的时候,简要的提到了定时器的执行情况。这里我们将对其进行更多的探讨。
setTimeout 和 setInterval 的区别
可能看到这个小标题大家第一时间想到的都是,setTimeout 只执行一次,而 setInterval 则是重复执行。Oh,no!我们今天当然不能讲的这么浅,我们需要谈到的是 迭代setTimeout 和 setInterval 的区别。
|
|
我们思考一下,上面这段代码,同样的两个 setTimeout
在 foo1
和 foo2
中执行情况会一样吗?如果不一样,为什么?
答案是——不一样!很简单,setTimeout
执行时机就不一样, bar1
会早于 bar2
进入任务队列。现在我们要来细细地看一下它们的执行时间相差几何。
假设任务队列为空,这是我们的理想环境,就像物理课上我们假设小木块与冰面的摩擦力为 0 。
如果继续假设 statement1
的执行时间为 50ms ,foo1
中,会于 50ms 时启动定时器,并于 150ms 时将 bar1
插入任务队列并立即被读取执行, foo2
会于 0ms 处启动定时器,但是需要 100ms 处才能被插入任务队列,此时 statement1
已经执行完了,所以可以立即读取。他们相差 50ms 。
如果假设 statement1
的执行时间为 200ms ,foo1
中,会于 200ms 时启动定时器,并于 300ms 时将 bar1
插入任务队列并立即被读取执行, foo2
会于 0ms 处启动定时器,但是需要 100ms 处才能被插入任务队列。此时 statement1
还没有执行完毕,需要继续等到,到 200ms 时可以读取任务队列中的 bar2
。他们相差 100ms 。
所以我们可以得出结论,bar1
和 bar2
的执行时间相差的是 statement1执行时间 和 定时器设定时间 两者中较小的那个。
而我们使用迭代定时器的时候大多是像 foo1
那样使用,因为在进行下一次的 setTimeout
之前,我们会有一些必要的操作以及判断。而这也就代表着,两次 setTimeout
之间的间隔是 >= 指定时间的。
setInterval
则不然,它不管你的回调函数里面都需要怎样的操作,它会很准时的每隔指定的时间就去向任务队列中添加任务的。
setInterval 的缺点
setInterval
的定时的准确性,看似完美,实则不然。我们回顾一下上一篇文章中展示的例子,首先,如果当前队列中有相同的 setInterval
实例,则无法继续添加,也就是说,可能会造成 setInterval
的执行次数的遗漏。再者,如果 setinterval
之前被阻塞了,那么则会造成接下来的两次 setinterval
的执行间隔时间小于指定时间。
因为这两个特性,所以很多人都会建议说少用 setInterval
,改用迭代 setTimeout
。但是诚如上面我们看到的, setTimeout
也不如我们所想象的那么完美,那么我们有没有什么更好的解决方法呢?
requestAnimationFrame
大多显示器的显示频率是 60HZ ,也就是每次刷新间隔 16.7ms ,如果我们使用 setTimeout/setInterval
的间隔小于 16.7ms ,这就要造成显示器重绘的堵塞压力,每隔一段时间就会丢失掉一次定时器的执行绘制。这也就是为何 setTimeout
的定时器推荐最小使用 16.7ms 。
requestAnimationFrame
即时顺应时代的浪潮而出生的,它和上面那两位最大的区别是,它的执行间隔时间是不由我们来控制的,只跟着显示器的绘制间隔走,显示器多久绘制一次,它就多久执行一次。
当然,它的妙用可不仅仅只是这些,如果同时有几个 requestAnimationFrame
,页面重绘之前,会通知浏览器把需要执行的几个函数执行一下(它们可能在不同的 requestAnimationFrame
中),执行好了之后一并绘制,而在 setTimeout
中则不行,大家排队,挨个执行然后重绘。
而且如果页面最小化了,或者被切换到其他 Tab 了, requestAnimationFrame
会暂停执行,优化资源的利用。
但是它不支持 IE9 及之前的版本哦~
最后我们就放个实例吧。
定时器东西不多,这就讲完了,下一篇文章可以看一下定时器的一些应用~