JavaScript中MutationObServer監聽DOM元素詳情
一、基本使用
可以通過MutationObserver構造函數實例化,參數是一個回調函數。
let observer = new MutationObserver(() => console.log("change")); console.log(observer);
observer對象原型鏈如下:
MutationObserver實例:
可以看到有disconnect
、observer
、takeRecords
方法。
1. observer方法監聽
observer
方法用於關聯DOM
元素,並根據相關設置進行監聽。
語法如下:
// 接收兩個參數 observer(DOM元素, MutationObserverInit對象);
其中:
- 第一個參數DOM元素就是頁面元素,比如:body、div等。
- 第二個參數就是設置要監聽的范圍。比如:屬性、文本、子節點等,是一個鍵值對數組。
示例1,監聽body元素class的變化:
let observer = new MutationObserver(() => console.log("change")); // 監聽body元素的屬性變化 observer.observe(document.body, { attributes: true }); // 更改body元素的class,會異步執行創建MutationObserver對象時傳入的回調函數 document.body.className = "main"; console.log("修改瞭body屬性"); // 控制臺輸出: // 修改瞭body屬性 // change
上面 change
的輸出是在 修改瞭body
屬性 之後,可見註冊的回調函數是異步執行的,是在後面執行的。
2. 回調函數增加MutationRecord實例數組參數
現在回調函數非常簡單,就是輸出一個字符串,看不出到底發生瞭什麼變化。
其實回調函數接收一個 MutationRecord
實例數組,實務中可以通過這個查看詳細的信息。
let observer = new MutationObserver( // 回調函數是一個 MutationRecord 實例數組。格式如下: // [MutationRecord, MutationRecord, MutationRecord, ...] (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); document.body.className = "main"; console.log("修改瞭body屬性"); // 控制臺輸出: // 修改瞭body屬性 // (1) [MutationRecord]
其中 mutationRecords信息 如下:
MutationRecord實例:
其中幾個比較關鍵的信息:
attributeName
表示修改的屬性名稱target
修改的目標type
類型
如果多次修改body的屬性,那麼會有多條記錄:
// MutationRecord let observer = new MutationObserver( // 回調函數接收一個 MutationRecord 實例,是一個數組。 (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 修改三次 document.body.className = "main"; document.body.className = "container"; document.body.className = "box"; // 控制臺打印如下: // (3) [MutationRecord, MutationRecord, MutationRecord]
註意:
這裡不是修改一次就執行一次回調,而是每修改一次就往 mutationRecords 參數加入一個 MutationRecord 實例,最後執行一次回調打印出來。
如果修改一次就執行一次回調,那麼性能就會比較差。
3. disconnect方法終止回調
如果要終止回調,可以使用disconnect方法。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 第一次修改 document.body.className = "main"; // 終止 observer.disconnect(); // 第二次修改 document.body.className = "container"; // 沒有日志輸出
這裡沒有日志輸出,包括第一次修改也沒有日志輸出,因為回調函數的執行是異步的,是在最後執行的。後面把observer
終止瞭,所以就不會執行瞭。
可以用setTimeout控制最後才終止,這樣回調就會正常執行。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 第一次修改 document.body.className = "main"; // 終止 setTimeout(() => { observer.disconnect(); // 第三次修改,下面修改不會回調瞭 document.body.className = "container"; }, 0); // 第二次修改 document.body.className = "container"; // 頁面輸出: // (2) [MutationRecord, MutationRecord]
終止之後再啟用
終止瞭之後可以再次啟動,請看下面示例:
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 第一次修改,會入 mutationRecords 數組 document.body.className = "main"; // 終止 setTimeout(() => { observer.disconnect(); // 第二次修改,因為終止瞭,下面修改不會入 mutationRecords 數組 document.body.className = "container"; }, 0); setTimeout(() => { // 再次啟用 observer.observe(document.body, { attributes: true }); // 修改body屬性,會入 mutationRecords 數組 document.body.className = "container"; }, 0); // 控制臺輸出: // [MutationRecord] // [MutationRecord]
這邊回調函數是執行瞭兩次,打印瞭兩個,其中:
- 第一個輸出是在第一次修改,後面沒有同步代碼瞭,就執行瞭回調。
- 第二個輸出是在第三次修改,因為重新啟用瞭,所以就正常執行瞭回調。
第二次修改,因為observer
被終止瞭,所以修改body
的屬性不會入 mutationRecords
數組。
4. takeRecords方法獲取修改記錄
如果希望在終止observer
之前,對已有的 mutationRecords
記錄進行處理,可以用takeRecords
方法獲取。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 第一次修改,會入 mutationRecords 數組 document.body.className = "main"; // 第二次修改,會入 mutationRecords 數組 document.body.className = "container"; // 第三次修改,會入 mutationRecords 數組 document.body.className = "box"; // 取到修改記錄,可以對其進行處理 let mutationRecords = observer.takeRecords(); console.log(mutationRecords); // 控制臺打印: // (3) [MutationRecord, MutationRecord, MutationRecord] console.log(observer.takeRecords()); // 控制臺打印: // [] // 終止 observer.disconnect();
二、監聽多個元素
上面監聽都是隻有一個元素,如果要監聽多個元素可以復用MutationObserver
實例
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 創建 div1 元素,並監聽 let div1 = document.createElement("div"); observer.observe(div1, { attributes: true }); div1.id = "box1"; // 創建div2並監聽 let div2 = document.createElement("div"); observer.observe(div2, { attributes: true }); div2.id = "box2"; // 控制臺打印: // (2) [MutationRecord, MutationRecord]
控制臺打印瞭兩個MutationRecord,其中:
- 第一個
MutationRecord
就是 div1 的id屬性修改記錄。 - 第二個
MutationRecord
就是 div2 的id屬性修改記錄。
其他使用方式和上面的類似。
三、監聽范圍MutationObserverInit對象
上面的監聽都是監聽屬性,當然也可以監聽其他的東西,比如:文本、子節點等。
1. 觀察屬性
上面的例子都是觀察元素自有的屬性,這裡再舉一個自定義屬性的例子。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true }); // 修改自定義的屬性 document.body.setAttribute("data-id", 1); // 控制臺打印: // [MutationRecord]
修改自定義的屬性一樣會加入到 mutationRecords
數組。
另外值的一提的是 data-id
經常用來給元素標記一些數據啥的,如果發生變化,程序就可以監聽到,就可以處理一些相應的邏輯。
attributeFilter過濾:
如果要監聽指定的屬性變化,可以用 attributeFilter
過濾。
let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); observer.observe(document.body, { attributes: true, // 設置白名單 attributeFilter: ["data-id"] }); // 修改白名單 attributeFilter 內的屬性,會入 mutationRecords document.body.setAttribute("data-id", 1); // 修改不在白名單 attributeFilter 內的屬性,不會入 mutationRecords document.body.setAttribute("class", "main"); // 控制臺打印: // [MutationRecord]
attributeOldValue記錄舊值:
如果要記錄舊值,可以設置 attributeOldValue
為true
。
let observer = new MutationObserver( // MutationRecord對象中oldValue表示舊值 (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue)) ); observer.observe(document.body, { attributes: true, attributeOldValue: true, }); // 第一次修改,因為原來沒有值,所以舊值 oldValue = null document.body.setAttribute("class", "main"); // 第二次修改,因為前面有改瞭一次,所以舊值 oldValue = main document.body.setAttribute("class", "container"); // 控制臺打印: // (2) [null, 'main']
2. 觀察文本
觀察文本設置 characterData
為 true
即可,不過隻能觀察文本節點。
請看如下示例:
<!-- 一個性感的div --> <div id="box">Hello</div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 獲取文本節點 let textNode = document.getElementById("box").childNodes[0]; observer.observe(textNode, { // 觀察文本變化 characterData: true }); // 修改文本 textNode.textContent = "Hi"; // 控制臺打印: // [MutationRecord] </script>
如果直接監聽div元素,那麼是不生效的:
<!-- 一個性感的div --> <div id="box">Hello</div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 監聽div不會生效 let box = document.getElementById("box"); observer.observe(box, { characterData: true }); box.textContent = "Hi"; // 控制臺無輸出 </script>
characterDataOldValue記錄舊值:
如果要記錄文本舊值,可以設置 characterDataOldValue
為true
。
<!-- 一個性感的div --> <div id="box">Hello</div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue)) ); // 獲取文本節點 let textNode = document.getElementById("box").childNodes[0]; observer.observe(textNode, { // 觀察文本變化 characterData: true, // 保留舊數據 characterDataOldValue: true, }); // 修改文本兩次 textNode.textContent = "Hi"; textNode.textContent = "Nice"; // 控制臺打印: // (2) ['Hello', 'Hi'] </script>
因為div內的內容原本為Hello,先修改為Hi,又修改為Nice,所以兩次修改的舊值就為:Hello 和 Hi 瞭。
3. 觀察子節點
MutationObserver
實例也可以觀察目標節點子節點的變化。
<!-- 一個性感的div --> <div id="box">Hello</div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 獲取div let box = document.getElementById("box"); observer.observe(box, { // 觀察子節點變化 childList: true, }); // 添加元素 let span = document.createElement("span") span.textContent = "world"; box.appendChild(span); // 控制臺打印: // [MutationRecord] </script>
MutationRecord
中的addedNodes
屬性記錄瞭增加的節點。
移除節點:
<!-- 一個性感的div --> <div id="box">Hello</div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 獲取div let box = document.getElementById("box"); observer.observe(box, { // 觀察子節點變化 childList: true, }); // 移除第一個子節點,就是Hello文本節點 box.removeChild(box.childNodes[0]); // 控制臺打印: // [MutationRecord] </script>
MutationRecord
中的removedNodes
屬性記錄瞭移除的節點。
移動節點:
對於已有的節點進行移動,那麼會記錄兩條MutationRecord
記錄,因為移動現有的節點是先刪除,後添加。
<!-- 一個性感的div --> <div id="box">Hello<span>world</span></div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); // 獲取div let box = document.getElementById("box"); observer.observe(box, { // 觀察子節點變化 childList: true, }); // 將span節點移動到Hello節點前面 box.insertBefore(box.childNodes[1], box.childNodes[0]); // 移動節點,實際是先刪除,後添加。 // 控制臺打印: // (2) [MutationRecord, MutationRecord] </script>
4. 觀察子樹
上面觀察的節點都是當前設置的目標節點,比如body
,就隻能觀察body
元素和其子節點的變化。
如果要觀察body
及其所有後代節點的變化,那麼可以設置subtree
屬性為true
。
<!-- 一個性感的div --> <div id="box">Hello<span>world</span></div> <script type="text/javascript"> let observer = new MutationObserver( (mutationRecords) => console.log(mutationRecords) ); let box = document.getElementById("box"); observer.observe(box, { attributes: true, // 觀察子樹的變化 subtree: true }); // span元素的id屬性變化就可以觀察到 box.childNodes[1].id = "text"; // 控制臺打印: // [MutationRecord] </script>
subtree
設置為true
後,不光div元素本身,span
元素也可以觀察到瞭。
總結:
- 1.
MutationObserver
實例可以用來觀察對象。 - 2.
MutationRecord
實例記錄瞭每一次的變化。 - 3. 回調函數需要所有腳本任務完成後,才會執行,即采用異步方式。
- 4. 可以觀察的訪問有屬性、文本、子節點、子樹。
到此這篇關於JavaScript
中MutationObServer
監聽DOM元素詳情的文章就介紹到這瞭,更多相關JavaScript中MutationObServer監聽DOM元素內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Vue.nextTick純幹貨使用方法詳解
- JS中 querySelector 與 getElementById 方法區別
- PerformanceObserver自動獲取首屏時間實現示例
- JavaScript Dom對象的操作
- JavaScript DOM API的使用教程及綜合案例