一、浏览器的消息队列
每个渲染进程都有一个主进程,同时页面中的大部分任务都是在主线程上执行的,包括:
- 渲染事件(解析DOM,计算布局,绘制);
- 用户交互事件(鼠标点击、滚动页面、放大缩小)
- JavaScript脚本执行
如果遇到既要处理JavaScript,又要处理各种输入,还要渲染页面的情况。就需要消息队列这种结构来统筹调度。队列是一种先进先出的结构。浏览器的事件循环就是在主线程运行过程中,有产生新的任务就压入队列,然后循环地从队列中取出任务并执行。
- 对于高优先级情况。例如:遇到监控DOM变化根据这些变化处理相应逻辑的情况,这些变化就会被压入队列一个一个排队等待执行。会影响实时性和执行效率。针对这种情况,就产生了微任务。消息队列里面存放着微任务,如果DOM有变化,那么就将该变化添加到微任务中。以先进先出的方式将消息队列中的微任务循环执行。
- 对于单个任务执行时间过长的情况。一般是通过回调来解决。比如setTimeout,为了让它的回调在特定的时间执行,这些回调会被放入另外一个需要延迟执行的消息队列。
以上两个队列是渲染进程内部维护的众多消息队列中比较常见的两个:普通队列和延迟队列。主线程采用一个for循环,依次将这些消息队列中的人物取出并执行。
二、微任务队列
上面所提到的都是宏任务,在浏览器的时事件循环系统中除了宏任务还有微任务。微任务在JavaScript创建执行上下文的时候就会创建微任务队列。也就是说每一个宏任务都关联了一个微任务队列。那么什么情况下会产生微任务以及微任务的执行时机就很重要了。
- 产生微任务
使用MutationObserver监控某个DOM节点,使用promise,调用promise.resolve()或者promise.then()
- 微任务执行时机
在JavaScript执行完当前宏任务退出全局执行上下文并且清空调用栈的时候,JavaScript会检查微任务队列,然后按顺序执行队列中的微任务。如果微任务产生了新的微任务,同样会将新产生的微任务添加到微任务队列。然后循环微任务队列,直到队列为空。
- 监听DOM变化方法演化
以前监听DOM变化主要是通过Mutation Event这种观察者设计模式,通过捕捉DOM的变化触发响应事件的回调,这种方式属于同步触发。很快这种方式带来性能方面的问题。一旦有DOM的改变就会同步触发相应的方法使得性能严重浪费。后来使用MutationObserver引入微任务,将这些变化添加到微任务列表,实现一次触发异步调用。