一、MutationObserver 介面說明
此介面可以在 DOM 被修改時異步執行回呼,使用 MutationObserver 可以觀察整個檔案、DOM 樹的一部分,或某個元素,此外還可以觀察元素屬性、子節點、文本,或者前三者任意組合的變化,
DOM3 中新引進 MutationObserver 介面是為了取代廢棄的 DOM2 中的 MutationEvent,
二、基本用法
MutationObserver 的實體要通過 MutationObserver 建構式并傳入一個回呼函式來創建,
let observer = new MutationObserver(() => console.log('Dom was mutated~'))
1. observe() 方法
新創建的 MutationObserver 實體不會關聯 DOM 的任何部分,需要使用 ovserve()方法與 DOM 關聯起來,
此方法需傳兩個必須的引數:
- 要觀察其變化的 DOM 節點
- 一個 MutationObserverInit 物件
MutationObserverInit 物件:用于控制哪些方面的變化,是一個鍵/值對形式配置選項的字典,
// 創建一個觀察者(observer)并配置它觀察 body 元素上的屬性變化
let observer = new MutationObserver(() => {
console.log('body attributes changed~')
})
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
console.log('Changed body class')
// Changed body class
// body attributes changed~
執行上述代碼后,body 元素上的任何屬性發生變化都會被這個 MutationObserver 實體發現,然后會異步執行注冊的回呼函式,而 body 元素后代的修改或其他非屬性變化修改都不會觸發回呼進入任務佇列,
注意,回呼中的 console.log()是后執行的,這表明回呼并非與實際的 DOM 變化同步執行,
2. 回呼與 MutationRecord 引數
每個回呼都會受到一個 MutationRecord 實體的陣列,MutationRecord 實體包含資訊包括發生了什么變化、以及 DOM 的哪一部分受到了影響,回呼的第二個引數是 MutationObserver 的實體,
// 連續修改會生成多個MutationRecord實體,回呼執行時會受到包含所有這些實體的陣列,順序為變化事件順序,
let observer = new MutationObserver((MutationRecords, mutationObserver) => {
console.log(MutationRecords, mutationObserver)
})
observer.observe(document.body, { attributes: true })
document.body.setAttribute('foo', 'bar')
document.body.className = 'testName'
// [MutationRecord, MutationRecord], MutationObserver
執行效果如下圖:

MutationRecord 實體屬性說明
| 屬性 | 說明 |
|---|---|
| target | 被修改影響的目標節點 |
| type | 字串,表示變化的型別:"attributes"、"characterData"、"childList" |
| oldValue | 如果在 MutationObserverInit 物件中啟用(attributeOldValue 或 characterData OldValue 為 true),"attributes" 或 "characterData" 的變化事件會設定這個屬性為被替代的值"childList" 型別的變化始終將這個屬性設定為 null |
| attributeName | 對于 "attributes" 型別的變化,這里保存被修改屬性的名字,其他變化事件會將此設定為 null |
| attributeNamespace | 對于使用了命名空間的 "attributes" 型別的變化,這里保存被修改屬性的名字,其他變化事件會將此設定為 null |
| addedNodes | 對于 "childList" 型別的變化,回傳包含變化中添加節點的 NodeList,默認為空 NodeList |
| removedNodes | 對于 "childList" 型別的變化,回傳包含變化中洗掉節點的 NodeList,默認為空 NodeList |
| previousSibling | 對于 "childList" 型別的變化,回傳變化節點的前一個同胞 Node,默認為 null |
| nextSibling | 對于 "childList" 型別的變化,回傳變化節點的后一個同胞 Node,默認為 null |
3. disconnet() 方法
默認情況下,只要被觀察的元素不被垃圾回收,MutationObserver 的回呼就會回應 DOM 變化事件而執行,要提前終止回呼,可以呼叫 disconnet() 方法,
let observer = new MutationObserver(() => {
console.log('body attributes changed~')
})
observer.observe(document.body, { attributes: true })
observer.disconnet()
document.body.className = 'foo'
// (沒有日志輸出)
重用 MutationObserver:呼叫 disconnet() 方法并不會結束 MutationObserver 的生命,還可以重新使用這個觀察者,再講它關聯到新的目標節點,
三、MutationObserverInit 觀察范圍
MutationObserverInit 物件用于控制對目標節點的觀察范圍,例:屬性變化、文本變化和節點變化,
| 屬性 | 說明 |
|---|---|
| subtree | boolean,表示除了目標節點,是否觀察其子樹(后代),默認為 false,只觀察目標節點的變化 |
| attributes | boolean,表示是否觀察目標節點的屬性變化,默認為 false |
| attributeFilter | 字串陣列,表示要觀察哪些屬性的變化,把這個值設定為 true,也會將 attributes 值轉換為 true,默認為觀察所有屬性 |
| attributeOldValue | boolean,表示 MutationRecord 是否記錄變化之前的值,把這個值設定為 true,也會將 attributes 值轉換為 true,默認為 false |
| characterData | boolean,表示修改字符資料是否觸發變化事件 |
| characterOldValue | boolean,表示 MutationRecord 是否記錄變化之前的值,把這個值設定為 true,也會將 characterData 值轉換為 true,默認為 false |
| childList | boolean,表示修改目標節點的子節點是否觸發變化事件,默認為 false |
呼叫 observe() 時,MutationObserverInit 物件中 attributes、characterData、childList 屬性必須至少有一項為 true,
<!-- 觀察子節點 -->
<body>
<div id="con"></div>
<script>
const conEle = document.getElementById('con')
let observer = new MutationObserver((MutationRecords, mutationObserver) => {
console.log(MutationRecords)
})
observer.observe(conEle, { childList: true })
conEle.appendChild(document.createElement('p'))
</script>
</body>
// [MutationRecord]
列印效果如下圖:

四、異步回呼與記錄佇列
1. 記錄佇列
每次 MutationRecord 被添加到 MutationObserver 的記錄佇列時,僅當之前沒有已排期的微任務回呼時(佇列中微任務長度為 0),才會將觀察者注冊的回呼(在初始化 MutationObserver 時傳入)作為微任務調度到任務佇列上,這樣可以保證記錄佇列的內容不會被回呼處理兩次,
不過在回呼的微任務異步執行期間,有可能又會發生更多的變化事件,因此被處理的回呼會接收到一個 MutationRecord 實體的陣列,順序為它們進入記錄佇列的順序,回呼要負責處理這個陣列的每一個實體,因為函式退出之后這些實體就不存在了,回呼執行后,這些 MutationRecord 就用不著了,因此記錄佇列會被清空,其內容會被丟棄,
2. takeRecords() 方法
呼叫 MutationObserver 實體的 takeRecords() 方法可以清空記錄佇列,取出并回傳其中的所有 MutationRecord 實體,
這在希望斷開與觀察目標的聯系,但有希望處理由于呼叫 disconnet() 而被拋棄的記錄佇列中的 MutationRecord 實體時比較有用,
let observer = new MutationObserver((MutationRecords) => {
console.log(MutationRecords)
})
observer.observe(document.body, { attributes: true })
document.body.className = 'foo'
document.body.className = 'bar'
console.log(111, observer.takeRecords())
console.log(222, observer.takeRecords())
// 111 [MutationRecord, MutationRecord]
// 222 []
五、記憶體與垃圾回收
將變化回呼委托給微任務來執行可以保證事件同步觸發,同時避免隨之而來的混亂,為 MutationObserver 而實作的記錄佇列,可以保證即使變化事件被爆發式的觸發,也不會顯著的拖慢瀏覽器,
無論如何,使用 MutationObserver 仍然不是沒有代價的,因此理解什么時候避免出現這種情況就很重要了,
1. MutationObserver 的參考
MutationObserver 實體與目標節點之間的參考關系是非對稱的,MutationObserver 擁有對觀察目標節點的弱參考,因為是弱參考,所以不會妨礙垃圾回收程式回收目標節點,
然而,目標節點卻擁有對 MutationObserver 的強參考,如果目標節點從 DOM 中被移除,隨后被垃圾回收,則關聯的 MutationObserver 也會被垃圾回收,
2. MutationRecord 的參考
記錄佇列中的每個 MutationRecord 實體至少包含對已有 DOM 節點的一個參考,如果變化是 childList 型別,則會包含多個節點的參考,記錄佇列和回呼處理的默認行為是耗盡這個佇列,處理每個 MutationRecord,然后讓它們超出作用域并被垃圾回收,
有時候可能需要保存某個觀察者的完整變化記錄,保存這些 MutationRecord 實體,也就會保存它們參考的節點,因而會妨礙這些節點的回收,如果需要盡快地釋放記憶體,建議從每個 MutationRecord 中抽取最有用的資訊,然后保存到一個新物件中,最后拋棄 MutationRecord,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/400321.html
標籤:其他
