JavaScript异步机制
2016-06-30 16:59
190 查看
单线程异步执行的JavaScript
JavaScript是单线程异步执行的,单线程意味着代码在任务队列中会按照顺序一个接一个的执行。异步代表JavaScript代码在任务队列 中的顺序并不完全等同于代码的书写顺序,比如事件绑定、Ajax、setTimeout()等任务的发生时间是“不可被预期”的。既然JavaScript是单线程机制,那Ajax为什么是异步的?setTimeout()是怎样执行的?
在浏览器中,JavaScript引擎是单线程执行的。也就是说,在同一时间内,只能有一段代码被JavaScript引擎执行。页面加载时,JavaScript引擎会顺序执行页面上所有JavaScript代码,优先执行同步代码。而异步代码由事件触发引擎按照“事件发生”的顺序添加到JavaScript引擎的任务队列中,待所有同步代码执行结束后,JavaScript引擎会按照任务队列中的顺序来执行异步代码。
下面是知乎上的一段回答:
JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序。
浏览器的内核是多线程的,它们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。
JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。
GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注 意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到 JavaScript引擎空闲时立即被执行。
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自 JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于 JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)。
了解JavaScript单线程异步执行的机制以后,再来看一看setTimeout()与setInterval()在执行时候的具体情况。
setTimeout()与setInterval()
setTimeout()
JavaScript引擎在执行setTimeout(fn, 10)时,一方面继续执行setTimeout(fn, 10)后面的同步代码,同时另一方面开始计时,在10ms之后将fn插入任务队列中。待所有同步代码执行结束后(JavaScript引擎空闲),依次任 务队列中的异步代码。所以,setTimeout(fn, 10)并不能准确的在10ms之后执行,而是大于等于10ms。看下面两段代码,会对setTimeout()的执行顺序有更直观的印象。
第一段:
代码执行的逻辑如图所示,纵向代表时间,左边表示同步代码的执行顺序,右边表示异步代码的任务队列,从左到后的箭头表示将异步代码插入任务队列。
第二段,将for循环上限去掉一个0:
两段代码的区别在于for循环执行的时间不同,第一段代码的for循环执行时间大于10ms,所以console.log('a')先被插入任务队 列,等for循环执行结束后,console.log('c')才被插入任务队列。第二段代码的for循环执行时间小于10ms,所以 console.log('c')先被插入任务队列。
setInterval()
setInterval()的执行方式与setTimeout()有不同。假如执行setInterval(fn, 10),则每隔10ms,定时器的事件就会被触发。与setTimeout()相同的是,如果当前没有同步代码在执行(JavaScript引擎空闲), 则定时器对应的方法fn会被立即执行,否则,fn就会被加入到任务队列中。由于定时器的事件是每隔10ms就触发一次,有可能某一次事件触发的时候,上一 次事件的处理方法fn还没有机会得到执行,仍然在等待队列中,这个时候,这个新的定时器事件就被丢弃,继续开始下一次计时。需要注意的是,由于 JavaScript引擎这种单线程异步的执行方式,有可能两次fn的实际执行时间间隔小于设定的时间间隔。比如上一个定时器事件的处理方法触发之后,等 待了5ms才获得被执行的机会。而第二个定时器事件的处理方法被触发之后,马上就被执行了。那么这两者之间的时间间隔实际上只有5ms。因 此,setInterval()并不适合实现精确的按固定间隔的调度操作。下面代码说明了这个问题:
可以看出,setInterval()前两次的间隔时间只有4ms。因为setInterval()第一次被触发后,里面的方法并没有马上被执行, 而是等待同步代码执行结束后才被执行,这个过程用了6ms。所以当第一次方法执行过后4ms,第二次方法也被执行了。从setInterval()第二次 被触发开始,后面几次的执行都没有被阻塞,所以间隔时间都在11ms左右。
总的来说,setTimeout()和setInterval()都不能满足精确的时间间隔。假如设定的时间间隔为10ms,则 setTimeout(fn, 10)中的fn执行的时间间隔可能大于10ms,而setInterval(fn, 10)中fn执行的时间间隔可能小于10ms。
相关文章推荐
- 推荐一个图片管理的html模版和简单的js交互
- Ajax加载HTML、XML、JSON对比分析
- iOS中JavaScript和OC交互
- jsonp
- 阻止js事件冒泡的例子(cancelBubble 、stopPropagation)
- 浏览器探针--JavaScript
- js 获取日期xxxx-xx-xx格式
- 如何将JS的变量传递给highcharts中series:data,感觉都是基于JS,怎么就不行呢?
- EL和JSTL
- 原生javascript控制兄弟节点
- 27、理解js的继承机制(转载自阮一峰)
- 百度地图API五:大批量多次坐标转换结果返回顺序问题
- 关于js里的this关键字的理解
- javascript中window.open()与window.location.href的区别
- 使用JS提交表单
- js点击button按钮跳转到另一个新页面
- jsp页面报错提示编码错误信息
- js用到的一些工具方法
- Javascript DOM Event对象方法详解
- retinajs 使用方法