MutationObserver 接口
DOM規范中的 MutationObserver 接口可以在DOM被修改時異步執行回調。
使用MutationObserver可以觀察整個文檔、DOM樹的一部分或某個元素,元素屬性、字節點、文本等。
新引進的MutationObserver接口取代了已廢棄的MutationEvent。
MutationObserver 實例
MutationObserver實例通過調用MutationObserver構造函數并傳入一個回調函數創建。
新創建的MutationObserver實例,需要使用observe()方法與DOM進行關聯。
observe()
兩個必填參數:
要觀察的DOM節點
MutationObserveInit 對象
MutationObserveInit對象用于控制觀察哪些方面的變化,是一個鍵值對形式配置選項的字典。
回調與 MutationRecord
MutationObserver(構造函數)中的參數(回調函數),每個回調都會收到一個MutationRecord實例的數組(第一個參數),記錄發生了什么變化以及DOM的哪部分收到了影響。
因為回調執行之前可能同時發生多個滿足觀察條件的事件,所以每次執行回調都會傳入一個包含按順序入隊的MutationRecord實例的數組。
new MutationObserver(fn)
參數:回調函數fn。
回調函數fn的參數:
第一個參數:MutationRecord實例的數組
第二個參數:觀察變化的MutationObserver的實例
MutationObserver
?的回調函數可以多次執行。每當觀察到 DOM 樹的變化時,回調函數就會被調用。這些變化包括但不限于節點的添加、刪除、屬性的更改等。
那么問題來了
本例中,為什么回調中的?console.log
?只輸出了一次?
let ob = new MutationObserver(function () {console.log("%cbody attributes changed", "color: red; font-weight:bold;font-size: 20px;");
});
ob.observe(document.body, { attributes: true });
document.body.setAttribute("foo", "bar");
document.body.className = "newClass";
解答(來自通義靈碼)
1.MutationObserver 是異步執行的
MutationObserver
?的回調是通過?微任務隊列(microtask queue)?異步觸發的。- 當你在短時間內連續修改 DOM 屬性時,瀏覽器會將這些變更合并為一個批次,并只調用一次回調函數。
2.?屬性變更被合并處理
- 在你的代碼中:
這兩個操作都是對?document.body.setAttribute("foo", "bar"); document.body.className = "newClass";
document.body
?的屬性進行修改。 - 因為它們在同一個事件循環中發生,所以?
MutationObserver
?只會收到一次“屬性變化”的通知,并觸發一次回調。
🧪 實驗驗證
如果你希望看到兩次日志輸出,可以使用?setTimeout
?將這兩個修改分開到不同的事件循環中:
document.body.setAttribute("foo", "bar"); setTimeout(() => { document.body.className = "newClass"; }, 0);
這樣,MutationObserver
?會在兩個獨立的 microtask 中檢測到兩次屬性變更,從而觸發兩次回調。
? 總結
現象 | 原因 |
---|---|
console.log ?只輸出一次 | 瀏覽器將多個 DOM 修改合并為一次變更通知,回調只執行一次 |
關鍵點 | 說明 |
---|---|
異步機制 | MutationObserver 使用微任務隊列處理回調 |
批量優化 | 瀏覽器自動合并多次變更以提升性能 |
控制輸出次數 | 可通過?setTimeout ?或其他方式分隔變更事件 |
🛠? 建議改進代碼
// 分開兩次 DOM 操作,確保觸發兩次回調 document.body.setAttribute("foo", "bar"); setTimeout(() => { document.body.className = "newClass"; }, 0);
這樣就能在控制臺看到兩次?"body attributes changed"
?的日志輸出。
再記錄一個通用示例
部分代碼來自夸克瀏覽器。
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>木木的學習筆記</title><style>#target {border: 1px solid black;padding: 10px;margin-top: 10px;}</style></head><body><div class="container"><div class="navBox"></div><p>MutationObserver 的回調函數可以多次執行。每當觀察到 DOM 樹的變化時,回調函數就會被調用。這些變化包括但不限于節點的添加、刪除、屬性的更改等。</p><p>測試</p><div id="target">這是一個目標元素。</div><br /><button onclick="changeText()">改變文本</button></div><script src="./js/nav.js"></script><script>//加載頁面導航var navObj = {title: "MutationObserver",chapter: "",section: "",part: "",};setNav(navObj);</script><script>// 選擇需要觀察變動的節點const targetNode = document.getElementById("target");// 配置觀察選項const config = { childList: true, subtree: true, attributes: true, characterData: true };// 創建一個回調函數,當觀察到DOM變動時會被調用const callback = function (mutationsList, observer) {console.log("DOM changed");for (let mutation of mutationsList) {if (mutation.type === "childList") {console.log("A child node has been added or removed.");} else if (mutation.type === "attributes") {console.log(`The ${mutation.attributeName} attribute was modified.`);} else if (mutation.type === "characterData") {console.log("Character data in the node has been changed.");}}};// 創建一個觀察器實例并傳入回調函數const observer = new MutationObserver(callback);// 開始觀察目標節點observer.observe(targetNode, config);// 函數用于改變目標元素的文本內容let count = 0;function changeText() {targetNode.textContent = `文本已被第 ${++count} 次改變!`;}</script></body>
</html>
這個示例中,每次修改都被記錄。
?