主要问题:
1、JS引擎是单线程,如何完成事件循环的?
2、定时器函数为什么计时不准确?
3、回调与异步,有什么联系和不同?
4、ES6的事件循环有什么变化?Node中呢?
5、异步控制有什么难点?有什么解决方案?
二、事件队列循环
(一)、浏览器线程
javascript引擎是基于事件驱动单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序,等待着任务队列中任务的到来,然后加以处理。
浏览器的内核是多线程的,它们在内核控制下相互配合协作,一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件线程。
GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。所以渲染操作的消耗特别大。
(二)、事件类型与队列
事件循环:引擎会创建一个类似于 while (true) 的无限循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。
任务队列:异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,webcore 包含上图中的3种 webAPI,分别是 DOM Binding、network、timer模块。
onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。
setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。
js定时器不准确的原因:
1、异步函数的回调执行会阻塞下一个循环tick。2、浏览器的时间精度不一。
ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
主线程:JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。
在这个过程提高页面响应速度的方式:减少主线程同步代码的数量,将不重要的代码转移到事件循环阶段执行。
(三)、ES6的任务队列
对事件队列进行了改造,使得异步回调可以更早的执行,每个tick间隙都会优先执行任务队列,不用排到事件队列的末尾。一道必考题,看一下执行顺序:
Promise实例具有三种状态:等待,决议,拒绝。由此产生的异步事件执行会作为任务队列挂在当前tick循环的末尾执行。
(四)、Nodo中的事件循环
JS引擎的事件循环都需要宿主环境提供队列维护,Node脱离了浏览器,用到了不同的方式和底层系统做交互。
观察者:引擎在每个循环过程中询问观察者是否有要处理的事件。
在Window下,观察者基于IOCP监听事件的完成情况;在*nix下基于多线程创建。
在node增加了异步执行的api,如process。nextTick()和setImmiediate。
process。nextTick()的回调函数保存在一个数组中,setImmiediate则是保存在一个链表中。
在每轮循环中,会将nextTick的数组中回调函数全部执行完,然后执行一个setImmiediate链表中的回调。
(五)、异步事件的处理
异步事件的处理过程中有执行时间、顺序不确定,回调地狱,错误难捕获定位等问题。
1、jQuery的 Deferred队列模块
JQ在 Deferred队列模块的基础上,模拟实现了promise相似的方法。可以做到链式回调,延迟访问。
2、ES6 Promise
promise实例决议后就不可更改,解决了信任问题。可以使用链式回调,随时访问异步事件的状态。相比之前要将回调交给另一方控制,promise可以将回调的执行控制在自己的逻辑中。至此仍未从实质上解决异步。
3、Generator
生成器函数实现了真正的异步控制,可以切换执行环境,并在执行环境之间传递变量,实现协作的函数线程。
著名的co模块,结合使用Promise和Generator实现了异步流程的同步编码形式。
4、async 函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。就是 Generator 函数的语法糖。
Node框架Koa采用了最新的async处理异步流程,使得编码更加简洁流畅。
推荐参考书:
《深入浅出NodeJS》
《jQuery技术内幕》
《Webkit技术内幕》
《你不知道的JavaScript》