本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!html
浏览器经过事件循环机制使页面“活”起来,在事件循环中宏任务和微任务有不一样的执行时机,而浏览器基于微任务的技术有 MutationObserver
、Promise
以及以 Promise
为基础开发出来的不少其余的技术。前端
前面的文章也花了很大的篇幅介绍 Promise
,那么这篇文章就带你们了解一下 MutationObserver
这个微任务是什么?用来作什么的吧!vue
MutationObserver
是用来监听 DOM 变化的一套方法,而监听 DOM 变化一直是前端工程师一项很是核心的需求。好比不少 Web 应用都利用 HTML 与 JavaScript 构建其自定义控件,与一些内置控件不一样,这些控件不是固有的。为了与内置控件一块儿良好地工做,这些控件必须可以适应内容更改、响应事件和用户交互。所以,Web 应用须要监视 DOM 变化并及时地作出响应。react
MutationObserver
是如今监听 DOM 变化的方法,那么在开始的时候是怎么监听的呢,了解监听 DOM 方法的演变有助于咱们更加深刻地理解浏览器是怎样运行的。git
在早期,浏览器并无提供对监听 DOM 的支持,因此那个时候要观察 DOM 是否变化,惟一能作的即是 轮询检测,好比使用 setTimeout
或者 setInterval
来定时检测 DOM 是否有改变。github
这种方式简单粗暴,可是会遇到两个问题:web
在 2000 年的时候引入了 Mutation Event
,它是在 DOM3 中定义的用于监听 DOM 树结构变化的事件,不过因为该事件存在兼容性以及性能上的问题已经被弃用。后端
Mutation Event
总共有7种事件:DOMNodeInserted
、DOMNodeRemoved
、DOMSubtreeModified
、DOMAttrModified
、DOMCharacterDataModified
、DOMNodeInsertedIntoDocument
和DOMNodeRemovedFromDocument
。设计模式
简单用法以下:数组
let box = document.getElementById('box')
box.addEventListener("DOMSubtreeModified", function () {
console.log('box 元素被修改');
}, false);
复制代码
Mutation Event
采用了 观察者的设计模式,当 DOM 有变更时就会马上触发相应的事件,这种方式属于 同步回调。
采用 Mutation Event
解决了 实时性 的问题,由于 DOM 一旦发生变化,就会当即调用 JavaScript 接口。可是 这种实时性形成了严重的性能问题,由于每次 DOM 变更,渲染引擎都会去调用 JS,这样会产生较大的性能开销。
好比利用 JS 动态建立或动态修改 50
个节点内容,就会触发 50
次回调,并且每一个回调函数都须要必定的执行时间,这里咱们假设每次回调的执行时间是 4ms
,那么 50
次回调的执行时间就是 200ms
,若此时浏览器正在执行一个动画效果,因为 Mutation Event
触发回调事件,就会致使动画的卡顿。
也正是由于使用 Mutation Event
会致使页面性能问题,因此 Mutation Event
被反对使用,并逐步从 Web 标准事件中删除了。
MutationObserver
API 能够用来监视 DOM 的变化,包括属性的变化、节点的增减、内容的变化等。
MutationObserver
的使用参考 MutationObserver
的 MDN 官方文档资料
MutationObserver
是一个构造器,用来实例化一个 Mutation
观察者对象,参数是一个回调函数,这个回调函数会在指定的 DOM 节点发送变化后执行,回调函数有两个参数:
mutations
:节点变化记录数组(MutationRecord
)observer
:观察者对象自己let observe = new MutationObserver(function (mutations, observer) {});
复制代码
MutationObserver
实例对象有三个方法,以下:
observe
:配置 MutationObserver
在 DOM 更改匹配给定选项时,经过其回调函数开始接收通知。即设置观察目标,接受两个参数:
target
:观察目标;options
:经过对象成员来设置观察选项disconnect
:阻止 MutationObserver
实例继续接收的通知,直到再次调用其 observe()
方法,该观察者对象包含的回调函数都不会再被调用。takeRecords
:从 MutationObserver
的通知队列中删除全部待处理的通知,并将它们返回到MutationRecord
对象的新 Array
中。即清空记录队列并返回里面的内容。使用实例:
// 选择须要观察变更的节点
const targetNode = document.getElementById('box');
// 观察器的配置(须要观察什么变更)
const config = {
attributes: true,
childList: true,
subtree: true
};
// 当观察到变更时执行的回调函数
const callback = function (mutationsList, observer) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('有节点发生改变,当前节点的内容是:' + mutation.target.innerHTML);
} else if (mutation.type === 'attributes') {
console.log('修改了' + mutation.attributeName + '属性');
}
}
};
// 建立一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 以后,可中止观察
// observer.disconnect();
复制代码
MutationObserver
的改进优化MutationObserver
将响应函数改为异步调用,能够不用在每次 DOM 变化都触发异步调用,而是等屡次 DOM 变化后,一次触发异步调用,而且还会使用一个数据结构来记录这期间全部的 DOM 变化。这样即便频繁地操纵 DOM,也不会对性能形成太大的影响。综上所述,MutationObserver
采用了 异步 + 微任务 的策略来实现监听 DOM 的变化。
MutationObserver
和 Vue 中的 nextTick
Vue 中 nextTick
可让咱们在下次 DOM 更新循环结束以后执行延迟回调,用于得到更新后的 DOM。
那在 Vue 中是怎么实现 nextTick
的呢?
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的全部数据变动。若是同一个 watcher
被屡次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免没必要要的计算和 DOM 操做是很是重要的。而后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工做。
而异步回调咱们知道有宏任务(macrotasks
)和微任务(microtasks
)两种,那为了让 nextTick
更快的执行,那确定是优先选择微任务(microtasks
)的。要建立一个新的微任务(microtask
),会优先使用 Promise
,若是浏览器不支持,再尝试 MutationObserver
。实在不支持,就只能用 setTimeout
这个宏任务了。
Vue 中的异步更新队列 是这样说的:
至于 MutationObserver
是怎么模拟 nextTick
的,能够看 源码,其实就是建立一个 TextNode
并监听内容变化,而后要 nextTick
的时候去改一下这个节点的文本内容:
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
复制代码
这篇文章介绍了监听 DOM 变化技术方案的演化史,从轮询到 Mutation Event
再到最新使用的 MutationObserver
。MutationObserver
方案的核心就是采用微任务机制,有效地权衡了实时性和执行效率的问题。
最后还简单介绍了 MutationObserver
和 Vue 中 nextTick
的关系。