微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。
微任务的执行时长会影响到当前宏任务的时长。比如一个宏任务在执行过程中,产生了 100 个微任务,执行每个微任务的时间是 10 毫秒,那么执行这 100 个微任务的时间就是 1000 毫秒,也可以说这 100 个微任务让宏任务的执行时间延长了 1000 毫秒。所以你在写代码的时候一定要注意控制微任务的执行时长。
在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。
Mutation Event 采用了观察者的设计模 式,当 DOM 有变动时就会立刻触发相应的事件,这种方式属于同步回调。
采用 Mutation Event 解决了实时性的问题,因为 DOM 一旦发生变化,就会立即调用 JavaScript 接口。但也正是这种实时性造成了严重的性能问题,因为每次 DOM 变动,渲染引擎都会去调用 JavaScript,这样会产生较大的性能开销。比如利用 JavaScript 动态创 建或动态修改 50 个节点内容,就会触发 50 次回调,而且每个回调函数都需要一定的执行时间,这里我们假设每次回调的执行时间是 4 毫秒,那么 50 次回调的执行时间就是 200 毫秒,若此时浏览器正在执行一个动画效果,由于 Mutation Event 触发回调事件,就会导致动画的卡顿。 也正是因为使用 Mutation Event 会导致页面性能问题,所以 Mutation Event 被反对使用,并逐步从 Web 标准事件中删除了。
为了解决了 Mutation Event 由于同步调用 JavaScript 而造成的性能问题,从 DOM4 开 始,推荐使用 MutationObserver 来代替 Mutation Event。MutationObserver API 可以用来监视 DOM 的变化,包括属性的变化、节点的增减、内容的变化等。
那么相比较 Mutation Event,MutationObserver 到底做了哪些改进呢?
首先,MutationObserver 将响应函数改成异步调用,可以不用在每次 DOM 变化都触发异步调用,而是等多次 DOM 变化后,一次触发异步调用,并且还会使用一个数据结构来 记录这期间所有的 DOM 变化。这样即使频繁地操纵 DOM,也不会对性能造成太大的影响。
我们通过异步调用和减少触发次数来缓解了性能问题,那么如何保持消息通知的及时性呢? 如果采用 setTimeout 创建宏任务来触发回调的话,那么实时性就会大打折扣,因为上面我们分析过,在两个任务之间,可能会被渲染进程插入其他的事件,从而影响到响应的实时性。
这时候,微任务就可以上场了,在每次 DOM 节点发生变化的时候,渲染引擎将变化记录 封装成微任务,并将微任务添加进当前的微任务队列中。这样当执行到检查点的时候,V8 引擎就会按照顺序执行微任务了。
综上所述, MutationObserver 采用了“异步 + 微任务”的策略。
通过异步操作解决了同步操作的性能问题; 通过微任务解决了实时性的问题。