消息队列和事件循环:⻚⾯是怎么“活”起来的?

消息队列

事件循环和消息队列


宏任务

  • 渲染事件(如解析DOM、计算布局、绘制)
  • ⽤⼾交互事件(如⿏标点击、滚动⻚⾯、放⼤缩⼩等)
  • JavaScript脚本执⾏事件
  • ⽹络请求完成、⽂件读写完成事件

消息队列中宏任务⼤致流程:

  • 先从多个消息队列中选出⼀个最⽼的任务,这个任务称为oldestTask;
  • 然后循环系统记录任务开始执⾏的时间,并把这个oldestTask设置为当前正在执⾏的任务;
  • 当任务执⾏完成之后,删除当前正在执⾏的任务,并从对应的消息队列中删除掉这个oldestTask
  • 最后统计执⾏完成的时⻓等信息。

微任务

第⼀种是把异步回调函数封装成⼀个宏任务,添加到消息队列尾部,当循环系统执⾏到该任务的时候执⾏回 调函数

第⼆种⽅式的执⾏时机是在主函数执⾏结束之后、当前宏任务结束之前执⾏回调函数,这通常都是以微任务 形式体现的。

微任务就是⼀个需要异步执⾏的函数,执⾏时机是在主函数执⾏结束之后、当前宏任务结束之前。

当JavaScript执⾏⼀段脚本的时候,V8会为其创建⼀个全局执⾏上下⽂,在创建全局执⾏上下⽂的 同时,V8引擎也会在内部创建⼀个微任务队列,微任务队列就是⽤来存放微任务的


微任务是怎么产⽣的?

第⼀种⽅式是使⽤MutationObserver监控某个DOM节点,然后再通过JavaScript来修改这个节点,或者为 这个节点添加、删除部分⼦节点,当DOM节点发⽣变化时,就会产⽣DOM变化记录的微任务。

第⼆种⽅式是使⽤Promise,当调⽤Promise.resolve()或者Promise.reject()的时候,也会产⽣微任务。


微任务队列是何时被执⾏

在当前宏任务中的JavaScript快执⾏完成时,JavaScript引擎会检查全局执⾏上下⽂中的微任务队列,然后按照顺序执⾏队列中的 微任务


结论:

  • 微任务和宏任务是绑定的,每个宏任务在执⾏时,会创建⾃⼰的微任务队列
  • 微任务的执⾏时⻓会影响到当前宏任务的时⻓。
  • 在⼀个宏任务中,分别创建⼀个⽤于回调的宏任务和微任务,⽆论什么情况下,微任务都早于宏任务执 ⾏。

监听DOM变化⽅法演变

最开始使用setTimeout或者setInterval来定时检测DOM是否有改变。这种⽅ 式简单粗暴,但是会遇到两个问题:如果时间间隔设置过⻓,DOM 变化响应不够及时;反过来如果时间间 隔设置过短,⼜会浪费很多⽆⽤的⼯作量去检查DOM,会让⻚⾯变得低效。

后来采用观察者的设计模式,当DOM 有变动时就 会⽴刻触发相应的事件,这种⽅式属于同步回调。

再后来使⽤ MutationObserver,将响应函数改成异步调⽤,可以不⽤在每次DOM变化都触发异步调⽤,⽽是等多 次DOM变化后,⼀次触发异步调⽤

  • 通过异步操作解决了同步操作的性能问题;
  • 通过微任务解决了实时性的问题。