JavaScript中MutationObServer監(jiān)聽DOM元素詳情
一、基本使用
可以通過MutationObserver構造函數實例化,參數是一個回調函數。
let observer = new MutationObserver(() => console.log("change"));
console.log(observer);
observer對象原型鏈如下:
MutationObserver實例:

可以看到有disconnect、observer、takeRecords方法。
1. observer方法監(jiān)聽
observer方法用于關聯DOM元素,并根據相關設置進行監(jiān)聽。
語法如下:
// 接收兩個參數 observer(DOM元素, MutationObserverInit對象);
其中:
- 第一個參數DOM元素就是頁面元素,比如:body、div等。
- 第二個參數就是設置要監(jiān)聽的范圍。比如:屬性、文本、子節(jié)點等,是一個鍵值對數組。
示例1,監(jiān)聽body元素class的變化:
let observer = new MutationObserver(() => console.log("change"));
// 監(jiān)聽body元素的屬性變化
observer.observe(document.body, {
attributes: true
});
// 更改body元素的class,會異步執(zhí)行創(chuàng)建MutationObserver對象時傳入的回調函數
document.body.className = "main";
console.log("修改了body屬性");
// 控制臺輸出:
// 修改了body屬性
// change
上面 change 的輸出是在 修改了body屬性 之后,可見注冊的回調函數是異步執(zhí)行的,是在后面執(zhí)行的。
2. 回調函數增加MutationRecord實例數組參數
現在回調函數非常簡單,就是輸出一個字符串,看不出到底發(fā)生了什么變化。
其實回調函數接收一個 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]
注意:
這里不是修改一次就執(zhí)行一次回調,而是每修改一次就往 mutationRecords 參數加入一個 MutationRecord 實例,最后執(zhí)行一次回調打印出來。
如果修改一次就執(zhí)行一次回調,那么性能就會比較差。
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";
// 沒有日志輸出
這里沒有日志輸出,包括第一次修改也沒有日志輸出,因為回調函數的執(zhí)行是異步的,是在最后執(zhí)行的。后面把observer終止了,所以就不會執(zhí)行了。
可以用setTimeout控制最后才終止,這樣回調就會正常執(zhí)行。
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]
這邊回調函數是執(zhí)行了兩次,打印了兩個,其中:
- 第一個輸出是在第一次修改,后面沒有同步代碼了,就執(zhí)行了回調。
- 第二個輸出是在第三次修改,因為重新啟用了,所以就正常執(zhí)行了回調。
第二次修改,因為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();
二、監(jiān)聽多個元素
上面監(jiān)聽都是只有一個元素,如果要監(jiān)聽多個元素可以復用MutationObserver實例
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
// 創(chuàng)建 div1 元素,并監(jiān)聽
let div1 = document.createElement("div");
observer.observe(div1, {
attributes: true
});
div1.id = "box1";
// 創(chuàng)建div2并監(jiān)聽
let div2 = document.createElement("div");
observer.observe(div2, {
attributes: true
});
div2.id = "box2";
// 控制臺打?。?
// (2) [MutationRecord, MutationRecord]
控制臺打印了兩個MutationRecord,其中:
- 第一個
MutationRecord就是 div1 的id屬性修改記錄。 - 第二個
MutationRecord就是 div2 的id屬性修改記錄。
其他使用方式和上面的類似。
三、監(jiān)聽范圍MutationObserverInit對象
上面的監(jiān)聽都是監(jiān)聽屬性,當然也可以監(jiān)聽其他的東西,比如:文本、子節(jié)點等。
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 經常用來給元素標記一些數據啥的,如果發(fā)生變化,程序就可以監(jiān)聽到,就可以處理一些相應的邏輯。
attributeFilter過濾:
如果要監(jiān)聽指定的屬性變化,可以用 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 即可,不過只能觀察文本節(jié)點。
請看如下示例:
<!-- 一個性感的div -->
<div id="box">Hello</div>
<script type="text/javascript">
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
// 獲取文本節(jié)點
let textNode = document.getElementById("box").childNodes[0];
observer.observe(textNode, {
// 觀察文本變化
characterData: true
});
// 修改文本
textNode.textContent = "Hi";
// 控制臺打?。?
// [MutationRecord]
</script>
如果直接監(jiān)聽div元素,那么是不生效的:
<!-- 一個性感的div -->
<div id="box">Hello</div>
<script type="text/javascript">
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
// 監(jiān)聽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))
);
// 獲取文本節(jié)點
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. 觀察子節(jié)點
MutationObserver 實例也可以觀察目標節(jié)點子節(jié)點的變化。
<!-- 一個性感的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, {
// 觀察子節(jié)點變化
childList: true,
});
// 添加元素
let span = document.createElement("span")
span.textContent = "world";
box.appendChild(span);
// 控制臺打?。?
// [MutationRecord]
</script>
MutationRecord中的addedNodes屬性記錄了增加的節(jié)點。
移除節(jié)點:
<!-- 一個性感的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, {
// 觀察子節(jié)點變化
childList: true,
});
// 移除第一個子節(jié)點,就是Hello文本節(jié)點
box.removeChild(box.childNodes[0]);
// 控制臺打印:
// [MutationRecord]
</script>
MutationRecord中的removedNodes屬性記錄了移除的節(jié)點。
移動節(jié)點:
對于已有的節(jié)點進行移動,那么會記錄兩條MutationRecord記錄,因為移動現有的節(jié)點是先刪除,后添加。
<!-- 一個性感的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, {
// 觀察子節(jié)點變化
childList: true,
});
// 將span節(jié)點移動到Hello節(jié)點前面
box.insertBefore(box.childNodes[1], box.childNodes[0]);
// 移動節(jié)點,實際是先刪除,后添加。
// 控制臺打?。?
// (2) [MutationRecord, MutationRecord]
</script>
4. 觀察子樹
上面觀察的節(jié)點都是當前設置的目標節(jié)點,比如body,就只能觀察body元素和其子節(jié)點的變化。
如果要觀察body及其所有后代節(jié)點的變化,那么可以設置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. 回調函數需要所有腳本任務完成后,才會執(zhí)行,即采用異步方式。
- 4. 可以觀察的訪問有屬性、文本、子節(jié)點、子樹。
到此這篇關于JavaScript中MutationObServer監(jiān)聽DOM元素詳情的文章就介紹到這了,更多相關JavaScript中MutationObServer監(jiān)聽DOM元素內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

