Heading for the future

消息队列和事件循环(Event Loop)

 

产生原因

为什么会有消息队列和事件循环呢?首先最关键的一点在于JS是个单线程,并且主线程非常繁忙,既要处理 DOM,又要计算样式,还要处理布局,同时还需要处理 JavaScript 任务以及各种输入事件。要让这么多不同类型的任务在主线程中有条不紊地执行,这就需要一个系统来统筹调度这些任务,这个统筹调度系统就是消息队列和事件循环系统。

消息队列

作用

消息队列是一种数据结构,可以存放要执行的任务。它符合队列“先进先出”的特点,也就是说要添加任务的话,添加到队列的尾部;要取出任务的话,从队列头部去取。

规范⚠️

whatwg标准里,有如下几个比较重要的地方。

  • “An event loop has one or more task queues”。

一个事件循环有一个或者多个任务队列。

  • Task queues are sets not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task.

任务队列是set而不是queues,因为事件循环处理模型的第一步从选定的队列中获取第一个可运行的任务,而不是使第一个任务出队。消息队列其实不算是队列,因为有很多个任务队列。每一个任务队列才是一个队列,每个任务队列都是一组任务的集合。而对于每一个任务队列里的队列,其任务来源是一致的,或者说不同的任务来源会被推入到不同的任务队列。

  • every task source must be associated with a specific task queue,

即每种任务来源都必须关联一个任务队列,任务来源都有DOM操作,UI事件,网络事件等。都会被放到专门的队列里。上一轮事件循环结束后,会先选择一个高优先级的任务队列,然后取出任务的第一个任务,也因此而有了事件的优先级

whatag规范:https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

注意⚠️: task queue的区分,是表示几种逻辑上不直接相关联的task(比如用户UI操作,网络请求,DOM更新等),同一类的task执行是不能乱序的。不同类的则可以,这允许浏览器给予某种source更高的优先级。

实现

实际上Chrome只实现了一个消息队列和延迟队列。虽然WHATWG规范里说每种task source必须关联一个task queue,但是需要注意的是只有一个task queue所有的task source都关联到这一个task queue也是符合规范的,这里是留给浏览器自行处理的空间

注意⚠️:task queue和task source的关系是一对多,即一个task queue里面可以有多个不同来源的任务

事件循环

作用

在线程运行过程中,接收并执行新的任务,就需要采用事件循环机制。

消息队列和事件循环的配合♻️

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

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

                image.png

执行步骤

  • 若有其他进程通信则会通过IPC与IO线程进行消息传递,
  • IO线程接收到其他进程传进来的消息后,则会将其添加到消息队列尾部;
  • 渲染主线程循环地从消息队列头部中读取任务,执行任务;

流程图

image.png

注意⚠️:此图是根据个人理解所画,画的是WHATWG规范中的执行流程,在浏览器真正的实现中,例如Chrome,他的消息队列就是单纯的消息队列。里面不含有其他的消息队列。若理解的有不当之处还望指出。

存在的问题

如何处理高优先级任务?

添加了微任务队列来解决,通常我们把消息队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列,在执行宏任务的过程中,如果DOM有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的继续执行,因此也就解决了执行效率的问题。等宏任务中的主要功能都直接完成之后,这时候,渲染引擎并不着急去执行下一个宏任务,而是执行当前宏任务中的微任务,因为 DOM 变化的事件都保存在这些微任务队列中,这样也就解决了实时性问题。

如何解决单个任务执行时长过久的问题?

JavaScript 可以通过回调功能来规避这种问题,也就是让要执行的 JavaScript 任务滞后执行。

 

参考

浏览器工作原理与实践

whatwg规范

特别感谢🙏

感谢贺师俊贺老在知乎对我提问的关于消息队列问题的解惑🙏🙏🙏,回答链接👇

https://www.zhihu.com/question/412373735/answer/1390219701?utm_source=wechat_session&utm_medium=social&utm_oi=905902661734395904

posted @ 2020-08-04 22:58  一只菜鸟攻城狮啊  阅读(349)  评论(0编辑  收藏