代码改变世界

javascript 基础 --- 事件循环与消息队列

2019-05-26 16:49  *奋斗*  阅读(521)  评论(0)    收藏  举报

宏任务 --- 把宿主(浏览器、node.js)发起的任务称为宏任务;
微任务 --- JavaScript 引擎发起的任务称为微任务;

JavaScript 确实只有一个线程(由js引擎维护),这个线程用来解释和执行 JavaScript 代码,我们称之为“主线程”。
浏览器中还存在其它线程,例如:处理ajax、dom、定时器等,我们称他们为“工作线程”。同时浏览器还维护了一个消息队列,主线程会将执行过程中遇到的异步请求,发送给消息队列,等到主线程空闲时再来执行消息队列中的任务。

js引擎,主要由两部分组成,内存堆和调用栈。内存堆进行内存分配;调用栈是代码在栈帧中执行的地方。调用栈顺序执行主线程的代码,当调用栈为空时,js引擎就去消息队列里取消息,取到后就执行。

js运行时,我们经常用的一些API并不是js引擎提供的,例如:setTimeout,它们是运行时提供的。运行时负责给js引擎线程发送消息,只负责生产消息,不负责取消息。

消息队列和事件循环

主线程在执行过程中遇到了异步任务,就发起函数或者称为注册函数,通过event loop通知相应的工作线程,同时主线程继续往后执行,不会等待。等到工作线程完成了任务,event loop 会将消息添加到消息队列中,如果此时调用栈为空,就执行消息队列中排在最前面的消息,依次执行。
事件循环,就是主线程重复从消息队列中取消息、执行的过程。
需要注意的是,GUI线程与js引擎是互斥的,当js引擎执行时,GUI线程会被挂起,GUI更新会被保存在一个队列中,等到js引擎空闲时立即被执行。因此页面渲染都是在js引擎主线程调用栈为空时进行的。

其实,事件循环机制和消息队列机制的维护,是由事件触发线程控制的。事件触发线程,同样是浏览器引擎提供的,它会维护一个消息队列。js引擎线程遇到异步任务(dom事件监听、网络请求、setTimeout等),会交给相应的线程单独去维护异步任务,等待某个时机(计时器结束、网络请求成功、用户点击DOM),然后由事件触发线程将异步对应的回调函数,加入到消息队列中,消息队列中的回调函数等待执行。

同时,js引擎维护一个执行栈,同步代码会依次加入执行栈执行,结束会退出执行栈。如果执行栈里的任务执行完成,执行栈为空的时候,事件触发线程才会从消息队列取出一个任务(即:异步的回调函数)放入执行栈中执行。

执行顺序

一个线程中,事件循环是唯一的,但任务队列可以拥有多个。任务队列由分为“宏任务”和“微任务”。
宏任务大概包括:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI Rendering;
微任务大概包括:process.nextTick、Promise、MutationObserver(H5新特性);

遇到Promise时会创建微任务(.then()里面的回调),注意此时new promise构造函数中的代码还是同步执行的,只有.then中的回调会被添加到微任务队列。microtask必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染(task >> 渲染 >> 下一个task(从任务队列中取一个))。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务。也就是说,在某一个宏任务执行完后,在重新渲染与开始下一个宏任务之前,就会将在它执行期间产生的所有微任务都执行完毕(在渲染前)。

总结一下执行机制:

执行一个宏任务(栈中没有就从事件队列中获取);

执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;

宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行);

当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染;

渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务队列中获取);

也就是说,每完整的完成一次宏任务,就执行一次GUI渲染。另外,MutationObserver 是微任务。

个人统计【2】:2019-05-27、2019-05-28