MutationObserver 实现微任务原理分析

2,629 阅读1分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

MutationObserver API
MDN:接口提供了监视对DOM树所做更改的能力,创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。

并且该调用是一个微任务,在vue源码$nextTick里可以看到它的使用

// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
  characterData: true
})
timerFunc = () => {
  counter = (counter + 1) % 2
  textNode.data = String(counter)
}

该源码里首先new一个MutationObserver对象,将flushCallbacks作为回调函数传入,然后创建了一个值为1的文本节点,在使用MutationObserver的实例将文本节点observe监察起来。最后在微任务包装器timerFunc里执行修改了文本节点的值。根据API知道DOM发生变化时,监视器会调用创建实例时传入的回调函数

这里我们得出思路:修改DOM文本节点,MutationObserver监察到并调用了回调函数,实现微任务

但是为什么DOM变了之后执行的回调函数就是微任务呢?

下面我举个示例:

先看这段代码

<body>
  <div id="id"></div>
  
  <script>
  
    document.body.style.backgroundColor = 'red'

    document.querySelector('#id').innerText = 456

    // 延迟5s
    const time = Date.now()
    while(Date.now() - time < 5000){
      continue
    }

    document.body.style.backgroundColor = 'blue'
    
  </script>
</body>

你认为浏览器会有什么效果?
页面先变红色,div的文本显示456,然后等待5s后,页面变为蓝色?

当你在浏览器执行这段代码时,你会发现页面在5s之前不会有任何变化,在5s之后页面直接变为蓝色,并出现456。这是因为:
js对dom的操作是在 当前任务队列里的宏任务微任务都执行结束后才执行的。js代码执行是由js引擎线程负责的,dom样式更改是由GUI渲染线程负责的,所以两个线程是互斥的,造成了dom操作“异步”的效果,而且GUI渲染会进行优化,多个同一dom的操作会合并。

所以dom更改异步,就照成了在更改dom后执行回调函数也异步了,vue源码$nextTick也就使用了这一特性,配合MutationObserver API很好实现了微任务效果。

总结

  • MutationObserver API 接口提供了监视对DOM树所做更改的能力,创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用,并且该调用是一个微任务
  • js对dom的操作是在 当前任务队列里的宏任务微任务都执行结束后才执行的,可配合MutationObserver实现微任务