上节讲到DOM的操做是"同步"仍是"异步",怎么准确的监听DOM到底啥时候渲染成功了呢——MutationObserver。javascript
接口提供了监视对DOM树所作更改的能力。它被设计为旧的MutationEvents功能的替代品,该功能是DOM3 Events规范的一部分。html
来源:MDNvue
简单粗暴,就是监听DOM树的变更。java
那么,被代替的MutationEvents
是什么?git
MutationEvents
在MDN中也写到了,是被DOM Event
认可在API上有缺陷,反对使用。MutationEvents
的原理:经过绑定事件监听DOM 乍一看到感受很正常,那列一下相关监听的事件:DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
复制代码
原做者吐槽邮件github
观察者模式
就能够不错的搞定,因此,看MutationObserver
名字就知道了。由于本篇主要介绍MutationObserver
,做为MutationObserver
的设计原理,简单理解就是web
A想看新闻,A就先在B这'交钱(订阅)',之后有新闻B就给A送报纸,A挑想看的新闻segmentfault
MutationObserver
获得返回值(获得报纸) - 能够有无数的A来看BDOM
- 变动时发送新的内容(送新报纸) - B决定A固然,更详细的观察者模式不久的未来都会有的。windows
针对上面MutationEvents
的缺陷,来讲一下MutationObserver
的优点。数组
const mutationObserver = new MutationObserver(callback);
复制代码
MutationObserver
是构造函数,兼容难度低( IE11以上才支持 ),值得说明的一点,移动端兼容性更佳。
const mutationObserver = new MutationObserver((mutations, observer) => {
console.log(observer); // 观察者实例
console.log(mutations); // 变更数组
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
复制代码
callback
做为监听事件,返回两个固定参数mutations
和observer
。 mutations
- 变更数组 observer
- 观察者实例
具体要执行的函数呢,往下看
function editContent() {
const content = document.getElementById('content');
console.log(1);
// --------------------------
observer(); // 订阅
// --------------------------
content.style.background = 'lightblue';
content.style.background = 'red';
console.log(2);
content.innerHTML = 4433;
console.log(3);
const newNode = document.createElement('p');
newNode.innerHTML = 888888;
content.appendChild(newNode);
console.log(4);
}
复制代码
执行结果:
// 1
// 2
// 3
// 4
// MutationObserver {}
// (4)[MutationRecord, MutationRecord, MutationRecord, MutationRecord]
复制代码
mutations
参数将监听的DOM
的全部变动记录按执行顺序
封装成为一个数组
返回。DOM
下子元素
的变动记录上面已经看到如何经过MutationObserver
构造函数建立一个实例对象。 下一步要绑定被观察者
,以及须要观察哪些变更项。
启动监听,接收两个参数。
DOM
节点MutationEvents
茫茫多的事件吗,这里经过配置项完成)mutationObserver.observe(content, {
attributes: true, // Boolean - 观察目标属性的改变
characterData: true, // Boolean - 观察目标数据的改变(改变前的数据/值)
childList: true, // Boolean - 观察目标子节点的变化,好比添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
subtree: true, // Boolean - 目标以及目标的后代改变都会观察
attributeOldValue: true, // Boolean - 表示须要记录改变前的目标属性值
characterDataOldValue: true, // Boolean - 设置了characterDataOldValue能够省略characterData设置
// attributeFilter: ['src', 'class'] // Array - 观察指定属性
});
复制代码
注:
attributeFilter/attributeOldValue
优先级高于 attributes
characterDataOldValue
优先级高于 characterData
attributes/characterData/childList
(或更高级特定项)至少有一项为true
;忽略
或必须为true
附:开发API原文
中止观察。调用后再也不触发观察器,解除订阅 注:当完成监听后,尽可量记得解除订阅
清除变更记录。即再也不处理未处理的变更。该方法返回变更记录的数组,注意,该方法当即生效。
附:takeRecords变动记录字段内容MutationRecord
对象
/*
MutationRecord = {
type:若是是属性变化,返回"attributes",若是是一个CharacterData节点(Text节点、Comment节点)变化,返回"characterData",节点树变化返回"childList"
target:返回影响改变的节点
addedNodes:返回添加的节点列表
removedNodes:返回删除的节点列表
previousSibling:返回分别添加或删除的节点的上一个兄弟节点,不然返回null
nextSibling:返回分别添加或删除的节点的下一个兄弟节点,不然返回null
attributeName:返回已更改属性的本地名称,不然返回null
attributeNamespace:返回已更改属性的名称空间,不然返回null
oldValue:返回值取决于type。对于"attributes",它是更改以前的属性的值。对于"characterData",它是改变以前节点的数据。对于"childList",它是null
}
*/
复制代码
DOM渲染完成
vue
对于MutationObserver
的应用以前有提到,DOM渲染
遇到脚本阻塞
时会发生相似于"异步"的状况,影响对DOM的后续操做。 虽然能够用触发回流的方式解决,可是在复杂业务场景中/过量数据场景中并非十分优秀的选择。 既然MutationObserver
可以监听到DOM树
中子节点
的变化,那么利用这一点,能够监听document
或父节点
的DOM树变化。
小巧的栗子:
// html
<div id="content">66666</div>
// js
let time = 4;
let arr = new Array(time);
let content = document.getElementById('content');
let mutationObserver = new MutationObserver(obsCallback); // 建立实例
obs(); // 绑定被观察者
obstruct(); // 执行阻塞
// 完成建立
function obsCallback(mutations, observer) {
console.log(`建立完成!`);
console.log(observer); // 观察者实例
console.log(mutations); // 变更数组
}
function obs() {
mutationObserver.observe(content, {
childList: true,
subtree: true,
});
}
function obstruct() {
for (let i = 0; i < arr.length; i++) {
arr[i] = `<div>${i}</div>`;
}
arr.map((item, idx) => {
for(let i = 0; i < 3000; i++) console.log(1)
content.innerHTML += item;
});
}
复制代码
以前有一篇讲到contenteditable
属性,使DOM
可编辑,作富文本编辑器等应用。 对于此类的应用,例如过滤关键字或内容,阻止编辑(内容复原),以及没法删除的图片水印等一系列操做均可以简单实现(附1)
阻止编辑的简陋栗子:
// html
<div id="content">66666</div>
// js
function obsCallback(mutations, observer) {
console.log(observer); // 观察者实例
console.log(mutations); // 变更数组
mutations.forEach(mutation => {
if (mutation.target.contentEditable === 'true') {
mutation.target.setAttribute('contenteditable', 'false');
}
})
}
function obs() {
mutationObserver.observe(content, {
// attributes: true,
attributeFilter: ['contenteditable']
// characterData: true,
// childList: true,
// subtree: true,
});
}
复制代码
vue
对于MutationObserver
的应用vue
框架在vue2.0以前,对于MutationObserver
的应用在于nextTick
; 原理是利用了MutationObserver
异步回调函数在微任务队列中排列。 具体操做呢,建立一个新节点并观察,随意的更新一下它的内容就能够了。
什么?为啥是2.0以前,如今用了MessageChannel
,什么是MessageChannel
?那是下一个话题。
为何要用MutationObserver
,或者说它和Promise
与setTimeout
的区别在哪里。 vue
优先级是Promise
、 MutationObserver
、 setTimeout
。 当Promise
不兼容时选择MutationObserver
,从功能和性能角度来讲二者基本一致,只是实现略有麻烦,要新建一个节点随便动一下。 setTimeout
最后为了兼容备选使用,缘由以下。
缘由: MutationObserver
与Promise
属于微任务,setTimeout
属于宏任务; 在浏览器执行机制里,每当宏任务执行结束都会进行从新渲染,微任务则在当前宏任务中执行,能够最快的获得最新的更新,若是有对应的DOM操做(回想一下上一篇),在宏任务结束时会一并完成。 但若是使用setTimeout
宏任务,更新内容须要等待队列中前面的所有宏任务执行完毕,而且,若是其中更新内容中有DOM操做,浏览器会渲染两次。
被弃用的缘由: 一个兼容性BUG。对于iOS UIWebView
,页面运行一段时间会中断,目前原生的MutationObserver
并无良好的解决办法,若是将IOS10 Safari
和其余运行环境分开,有些画蛇添足。(换一个更好的兼容就是了) 原回复
参考引用: