上節講到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
和其餘運行環境分開,有些畫蛇添足。(換一個更好的兼容就是了) 原回覆
參考引用: