前言
前段时间刚看完《JS忍者秘籍》,虽说是15年出版的,有些东西是过时了,但像对原型链、闭包、正则、定时器之类的机制却是不会过时的,里面很多东西都讲的很细,还是值得一读的,本文将对这本书中对定时器机制的部分进行详细的解析,如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文可以有所收获。
个人博客了解一下:obkoro1.com
准备
阅读本文之前,推荐先阅读Js 的事件循环(Event Loop)机制以及实例讲解这篇文章来理解背后发生的事情,本文对事件循环机制不会很仔细的讲解。
定时器解决的问题:
由于JS的单线程特性,定时器提供了一种跳出单线程限制的方法,即让一段代码在一定毫秒之后,再异步执行。
设置和清除定时器:
直接引用忍者秘籍中的图片:
注意:
- 定时器的时间间隔设为0,也会有几毫秒的延迟。
- 在使用
setTimeout
和setInterval
的时候最好将其赋值给一个变量,以便取消定时器。 - 在使用
Vue
的时候,setTimeout
和setInterval
的this指向的是window对象,访问不到组件数据以及方法。 - 在使用
Vue
的时候,路由跳转并不会销毁setInterval
,但是组件已经销毁了,这会导致问题。 - 在执行线程中
setTimeout
/setInterval
无法保证准时执行回调函数的。 setInterval
调用有可能会被废弃以及setInterval
的连续执行
第三点和第四点的解决方法可以参考我之前写的Vue 实践过程中的几个问题。
接下来要讲的是第五点和第六点,这两点是最重要,也是本文要重点解析的内容。
执行线程中的定时器执行
下面来看忍者秘籍中的栗子:
让我们看看这里发生了什么事情:
- 首先在0毫秒的时候有一个持续18毫秒的js代码块要执行。
- 然后在0毫秒的时候设了两个10毫秒延迟的定时器,
setTimeout
以及setInterval
,setTimeout
先设定。 - 在第6毫秒的时候有一个发生了鼠标单击事件。
事件排队。
同时发生了这么多事情,由于js的单线程特性,当线程正在执行状态,有异步事件触发时,它就会排队,并且在线程空闲时才进行执行。
这里的异步事件包括:鼠标单击,定时器触发,ajax请求、promise等事件。
让我们回到栗子中:
栗子中首先有一个18毫秒的代码块要执行,在这18毫秒中只能执行这段代码块,其他事件触发了之后只能排队等待执行。
在代码块还在运行期间,第6毫秒的时候,发生了一个鼠标单击事件,以及第10毫秒时的setTimeout
和setInterval
两个处理程序,这三个事件不能立即执行,而是被添加到等待执行的队列中。
先进先出(先排队的先执行)
18毫秒的时候代码块结束执行,有三个任务在排队等待执行,根据先进先出的原则,此时会先执行click事件,setTimeout
和setInterval
将继续排队等待执行。
setInterval调用被废弃
在click事件执行时,第20毫秒处,第二个setInterval
也到期了,因为此时已经click事件占用了线程,所以setInterval
还是不能被执行,并且因为此时队列中已经有一个setInterval
正在排队等待执行,所以这一次的setInterval
的调用将被废弃。
浏览器不会对同一个setInterval处理程序多次添加到待执行队列。
setTimeout
/setInterval
无法保证准时执行回调函数
click事件在第28毫秒处结束执行,有两个任务(setTimeout
和setInterval
)正在等待执行,遵循先进先出的原则,setTimeout
早于setInterval
设定,所以先执行setTimeout
。
so:我们期望在第10毫秒处执行的setTimeout
处理程序,最终会在第28毫秒处才开始执行,这就是上文提到的setTimeout
/setInterval
无法保证准时执行回调函数。
在30毫秒处,setInterval
又触发了,因为队列中已经有setInterval
在排队,所以这次的触发又作废了。
setInterval的连续执行
setTimeout
执行结束,在第36毫秒处,队列中的setInterval
处理程序才开始执行,setInterval
需要执行6毫秒。
在第40毫秒的时候setInterval
再次触发,因为此时上一个setInterval
正在执行期间,队列中并没有setInterval
在排队,这次触发的setInterval
将进入队列等候。
所以:setInterval
的处理时长不能比设定的间隔长,否则setInterval
将会没有间隔的重复执行
第42毫秒的时候,第一个setInterval
结束,然后队列中的setInterval
立即开始执行,在48毫秒的时候完成执行。然后50毫秒的时候再次触发setInterval
,此时没有任务在排队,将会立即执行。
setTimeout按照一定的间隔周期性的触发定时器。
上文说了,setInterval
的处理时长不能比设定的间隔长,否则setInterval
将会没有间隔的重复执行。
但是对这个问题,很多情况下,我们并不能清晰的把控处理程序所消耗的时长,为了我们能按照一定的间隔周期性的触发定时器,忍者秘籍中提供了下面这种使用方法:
// 实际上我不止在忍者秘籍中见过,在很多地方都见过这种技术。
setTimeout(function repeatMe(){
// do something
setTimeout(repeatMe,10);
// 执行完处理程序的内容后,在末尾再间隔10毫秒来调用该程序,这样就能保证一定是10毫秒的周期调用
},10)
忍者秘籍中关于定时器的其他知识:
- 定时器不能非常细粒化的控制执行的时间,书中建议在15ms以上。
- 可以使用定时器来分解长时间运行的任务,这里可以自行谷歌。
任务队列只有排队这么简单吗?
事实上,关于任务队列并不是只有简单的排队而已,忍者秘籍中提到为了方便,使用了这个概念,如果希望能更清晰的了解背后的机制,再次推荐阅读一下:Js 的事件循环(Event Loop)机制以及实例讲解,
结语
这上面所有一切,都是由js单线程特性导致的,所以会有事件排队、先进先出、setInterval调用被废弃、定时器无法保证准时执行回调函数以及出现setInterval的连续执行,时刻记住这一特性,很多关于事件执行顺序的问题都能想的通,并且找出解决方法。
希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。
个人blog and 掘金个人主页,如需转载,请放上原文链接并署名。码字不易,感谢支持!本人写文章本着交流记录的心态,写的不好之处,不撕逼,但是欢迎指点。
如果喜欢本文的话,欢迎关注我的订阅号,漫漫技术路,期待未来共同学习成长。
以上2018.6.17
参考资料:
JS忍者秘籍第8章:驯服线程和定时器