-
微任务: 在实时性和效率之间做一个有效的权衡。
-
使用API: MutationObserver、Promise、基于Promise的一系列异步方法
宏任务
-
渲染事件、 用户交互事件、JS脚本执行事件(js执行callback回调)、网络请求完成、文件读写完成事件,(延迟队列中的setTimeout,XMLHttpRequest)
-
协调任务在主线程上执行,页面进程引入消息队列和事件循环机制,多个消息队列(普通消息队列 && 延迟执行队列)
-
消息队列上的任务为宏任务
-
先从多个消息队列中选出一个最老的任务,为 oldestTask
-
然后循环系统记录任务开始执行时间,并把这个oldestTask设置为当前正在执行的任务。
-
当任务执行完成之后,删除当前执行的任务,并从对应的消息队列中删除这个oldestTask
-
最后统计执行完成的时长等信息
-
宏任务对于时间精读较高的需求就难以胜任了,因为添加事件到消息队列和事件循环系统的控制都是系统操作的,JS代码不能准确掌握任务要添加到队列中的位置,所以很难控制开始执行的事件
-
如调用setTimeout来设置回调任务的时候,消息队列可能已经插入了很多系统级的任务
微任务
-
异步回调两种方式,一是把异步回调封装成一个宏任务,添加到消息队列尾部,通过事件循环系统执行该任务的时候执行回调函数, 另一种是执行时机在主函数执行结束之后,当前宏任务结束之前执行回调函数,通常以微任务的形式体现
-
微任务就是一个需要异步执行的函数,执行时机在主函数执行之后,当前宏任务结束之前。
-
V8引擎分析
JS执行脚本时,V8创建一个全局执行上下文,在创建的同时V8引擎内部也会创建一个微任务队列,用来存放微任务,在当前宏任务执行过程中会产生多个微任务,使用微任务队列保存微任务,这个微任务队列给V8引擎内部使用的,所以无法通过JS直接访问。
- 产生微任务两种方式
使用MutationObserver监控某个DOM节点,通过JS修改节点,节点发生变化的时候就会产生DOM变化记录的微任务 使用Promise,在调用Promise.resolve()或者Promise.reject(),也会产生微任务
-
宏任务的JS
快执行完成时
,JS引擎准备退出全局执行上下文并清空调用栈时,JS引擎会检查全局执行上下文中的微任务队列,按照顺序执行队列中的微任务,WHATWG把执行微任务的时间点称为检查点(为什么呢?因为前文说了JS在检查快退出的全局执行上下文时才会去检查微任务队列是否有微任务在队列中,这个时间点恰好是快执行完宏任务的时候,至于为什么这样说的很模糊,具体的得去看V8引擎的C++代码才能看到实现的时机) -
执行微任务过程,产生新微任务,同样也会微任务添加到微任务队列,V8引擎一直循环执行微任务队列中的任务(事件循环,直到执行完成标志位非true才退出),执行微任务过程中产生新的微任务不会推迟到下个宏任务中执行,而是正在当前宏任务中技术执行。也就是微任务队列接着执行微任务而已。
结论
- 微任务和宏任务是绑定的,每个宏任务执行时,都会创建自己的微任务队列(重点重点重点)
- 微任务执行时长会影响当前宏任务的时长
- 一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行
- MutationObserver是用来监听DOM变化的一套方法
- Web 应用需要监视 DOM 变化并及时地做出响应
- 早期检测DOM变化,只能
轮询
检测,使用定时器来检测DOM变化,缺点是检查时间间隔过长DOM变化不及时,时隔过短则会浪费很多无用的工作量检查DOM,还有一个是定时器宏任务下延时队列无法确定准确的检测时间。 - 2000年引入
Mutation Event
(观察者模式),DOM有变化就立刻触发对应事件,为同步回调。 - 但是实时性解决的同时,造成了严重的性能问题,JS动态创建或修改多个节点内容就会触发多次回调,执行时间过长,如果执行的是动画效果,则会导致严重的动画卡顿(rAF),web标准事件逐步删除此API。
- Mutation Observer采用异步方式监听DOM变化,包括属性的变化、节点增减、内容变化等。
- 多次DOM同步回调改为一次异步回调,可以使用一个数据结构存储期间DOM变化,即时频繁操作DOM也不会有太大的性能影响
- 若使用setTimeout创建宏任务来触发回调的话,两个任务期间可能被渲染进程插入其他事件影响实时性。
- 微任务上场,DOM节点发生变化时,渲染引擎将变化记录(前两点提到的数据结构)封装成微任务,放入微任务队列,等执行检查点时,V8引擎就按顺序执行微任务。
- MutationObserver采用 "异步+微任务"策略
异步操作解决了DOM在多个同步回调的性能问题 微任务解决了实时性问题
- 作业
function executor(resolve, reject) {
let rand = Math.random();
console.log(1)
console.log(rand)
if (rand > 0.5)
resolve()
else
reject()
}
var p0 = new Promise(executor);
var p1 = p0.then((value) => {
console.log("succeed-1")
return new Promise(executor)
})
var p3 = p1.then((value) => {
console.log("succeed-2")
return new Promise(executor)
})
var p4 = p3.then((value) => {
console.log("succeed-3")
return new Promise(executor)
})
p4.catch((error) => {
console.log("error")
})
console.log(2)