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

可以看到有disconnect、observer、takeRecords方法。
1. observer方法監(jiān)聽
observer方法用于關(guān)聯(lián)DOM元素,并根據(jù)相關(guān)設(shè)置進(jìn)行監(jiān)聽。
語(yǔ)法如下:
// 接收兩個(gè)參數(shù) observer(DOM元素, MutationObserverInit對(duì)象);
其中:
- 第一個(gè)參數(shù)DOM元素就是頁(yè)面元素,比如:body、div等。
- 第二個(gè)參數(shù)就是設(shè)置要監(jiān)聽的范圍。比如:屬性、文本、子節(jié)點(diǎn)等,是一個(gè)鍵值對(duì)數(shù)組。
示例1,監(jiān)聽body元素class的變化:
let observer = new MutationObserver(() => console.log("change"));
// 監(jiān)聽body元素的屬性變化
observer.observe(document.body, {
attributes: true
});
// 更改body元素的class,會(huì)異步執(zhí)行創(chuàng)建MutationObserver對(duì)象時(shí)傳入的回調(diào)函數(shù)
document.body.className = "main";
console.log("修改了body屬性");
// 控制臺(tái)輸出:
// 修改了body屬性
// change
上面 change 的輸出是在 修改了body屬性 之后,可見注冊(cè)的回調(diào)函數(shù)是異步執(zhí)行的,是在后面執(zhí)行的。
2. 回調(diào)函數(shù)增加MutationRecord實(shí)例數(shù)組參數(shù)
現(xiàn)在回調(diào)函數(shù)非常簡(jiǎn)單,就是輸出一個(gè)字符串,看不出到底發(fā)生了什么變化。
其實(shí)回調(diào)函數(shù)接收一個(gè) MutationRecord 實(shí)例數(shù)組,實(shí)務(wù)中可以通過(guò)這個(gè)查看詳細(xì)的信息。
let observer = new MutationObserver(
// 回調(diào)函數(shù)是一個(gè) MutationRecord 實(shí)例數(shù)組。格式如下:
// [MutationRecord, MutationRecord, MutationRecord, ...]
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
document.body.className = "main";
console.log("修改了body屬性");
// 控制臺(tái)輸出:
// 修改了body屬性
// (1) [MutationRecord]
其中 mutationRecords信息 如下:
MutationRecord實(shí)例:

其中幾個(gè)比較關(guān)鍵的信息:
attributeName表示修改的屬性名稱target修改的目標(biāo)type類型
如果多次修改body的屬性,那么會(huì)有多條記錄:
// MutationRecord
let observer = new MutationObserver(
// 回調(diào)函數(shù)接收一個(gè) MutationRecord 實(shí)例,是一個(gè)數(shù)組。
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
// 修改三次
document.body.className = "main";
document.body.className = "container";
document.body.className = "box";
// 控制臺(tái)打印如下:
// (3) [MutationRecord, MutationRecord, MutationRecord]
注意:
這里不是修改一次就執(zhí)行一次回調(diào),而是每修改一次就往 mutationRecords 參數(shù)加入一個(gè) MutationRecord 實(shí)例,最后執(zhí)行一次回調(diào)打印出來(lái)。
如果修改一次就執(zhí)行一次回調(diào),那么性能就會(huì)比較差。
3. disconnect方法終止回調(diào)
如果要終止回調(diào),可以使用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";
// 沒(méi)有日志輸出
這里沒(méi)有日志輸出,包括第一次修改也沒(méi)有日志輸出,因?yàn)榛卣{(diào)函數(shù)的執(zhí)行是異步的,是在最后執(zhí)行的。后面把observer終止了,所以就不會(huì)執(zhí)行了。
可以用setTimeout控制最后才終止,這樣回調(diào)就會(huì)正常執(zhí)行。
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
// 第一次修改
document.body.className = "main";
// 終止
setTimeout(() => {
observer.disconnect();
// 第三次修改,下面修改不會(huì)回調(diào)了
document.body.className = "container";
}, 0);
// 第二次修改
document.body.className = "container";
// 頁(yè)面輸出:
// (2) [MutationRecord, MutationRecord]
終止之后再啟用
終止了之后可以再次啟動(dòng),請(qǐng)看下面示例:
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
// 第一次修改,會(huì)入 mutationRecords 數(shù)組
document.body.className = "main";
// 終止
setTimeout(() => {
observer.disconnect();
// 第二次修改,因?yàn)榻K止了,下面修改不會(huì)入 mutationRecords 數(shù)組
document.body.className = "container";
}, 0);
setTimeout(() => {
// 再次啟用
observer.observe(document.body, {
attributes: true
});
// 修改body屬性,會(huì)入 mutationRecords 數(shù)組
document.body.className = "container";
}, 0);
// 控制臺(tái)輸出:
// [MutationRecord]
// [MutationRecord]
這邊回調(diào)函數(shù)是執(zhí)行了兩次,打印了兩個(gè),其中:
- 第一個(gè)輸出是在第一次修改,后面沒(méi)有同步代碼了,就執(zhí)行了回調(diào)。
- 第二個(gè)輸出是在第三次修改,因?yàn)橹匦聠⒂昧?,所以就正常?zhí)行了回調(diào)。
第二次修改,因?yàn)?code>observer被終止了,所以修改body的屬性不會(huì)入 mutationRecords 數(shù)組。
4. takeRecords方法獲取修改記錄
如果希望在終止observer之前,對(duì)已有的 mutationRecords 記錄進(jìn)行處理,可以用takeRecords方法獲取。
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
// 第一次修改,會(huì)入 mutationRecords 數(shù)組
document.body.className = "main";
// 第二次修改,會(huì)入 mutationRecords 數(shù)組
document.body.className = "container";
// 第三次修改,會(huì)入 mutationRecords 數(shù)組
document.body.className = "box";
// 取到修改記錄,可以對(duì)其進(jìn)行處理
let mutationRecords = observer.takeRecords();
console.log(mutationRecords);
// 控制臺(tái)打?。?
// (3) [MutationRecord, MutationRecord, MutationRecord]
console.log(observer.takeRecords());
// 控制臺(tái)打印:
// []
// 終止
observer.disconnect();
二、監(jiān)聽多個(gè)元素
上面監(jiān)聽都是只有一個(gè)元素,如果要監(jiān)聽多個(gè)元素可以復(fù)用MutationObserver實(shí)例
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";
// 控制臺(tái)打?。?
// (2) [MutationRecord, MutationRecord]
控制臺(tái)打印了兩個(gè)MutationRecord,其中:
- 第一個(gè)
MutationRecord就是 div1 的id屬性修改記錄。 - 第二個(gè)
MutationRecord就是 div2 的id屬性修改記錄。
其他使用方式和上面的類似。
三、監(jiān)聽范圍MutationObserverInit對(duì)象
上面的監(jiān)聽都是監(jiān)聽屬性,當(dāng)然也可以監(jiān)聽其他的東西,比如:文本、子節(jié)點(diǎn)等。
1. 觀察屬性
上面的例子都是觀察元素自有的屬性,這里再舉一個(gè)自定義屬性的例子。
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true
});
// 修改自定義的屬性
document.body.setAttribute("data-id", 1);
// 控制臺(tái)打?。?
// [MutationRecord]
修改自定義的屬性一樣會(huì)加入到 mutationRecords 數(shù)組。
另外值的一提的是 data-id 經(jīng)常用來(lái)給元素標(biāo)記一些數(shù)據(jù)啥的,如果發(fā)生變化,程序就可以監(jiān)聽到,就可以處理一些相應(yīng)的邏輯。
attributeFilter過(guò)濾:
如果要監(jiān)聽指定的屬性變化,可以用 attributeFilter 過(guò)濾。
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
observer.observe(document.body, {
attributes: true,
// 設(shè)置白名單
attributeFilter: ["data-id"]
});
// 修改白名單 attributeFilter 內(nèi)的屬性,會(huì)入 mutationRecords
document.body.setAttribute("data-id", 1);
// 修改不在白名單 attributeFilter 內(nèi)的屬性,不會(huì)入 mutationRecords
document.body.setAttribute("class", "main");
// 控制臺(tái)打?。?
// [MutationRecord]
attributeOldValue記錄舊值:
如果要記錄舊值,可以設(shè)置 attributeOldValue 為true。
let observer = new MutationObserver(
// MutationRecord對(duì)象中oldValue表示舊值
(mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))
);
observer.observe(document.body, {
attributes: true,
attributeOldValue: true,
});
// 第一次修改,因?yàn)樵瓉?lái)沒(méi)有值,所以舊值 oldValue = null
document.body.setAttribute("class", "main");
// 第二次修改,因?yàn)榍懊嬗懈牧艘淮?,所以舊值 oldValue = main
document.body.setAttribute("class", "container");
// 控制臺(tái)打?。?
// (2) [null, 'main']
2. 觀察文本
觀察文本設(shè)置 characterData 為 true 即可,不過(guò)只能觀察文本節(jié)點(diǎn)。
請(qǐng)看如下示例:
<!-- 一個(gè)性感的div -->
<div id="box">Hello</div>
<script type="text/javascript">
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
// 獲取文本節(jié)點(diǎn)
let textNode = document.getElementById("box").childNodes[0];
observer.observe(textNode, {
// 觀察文本變化
characterData: true
});
// 修改文本
textNode.textContent = "Hi";
// 控制臺(tái)打印:
// [MutationRecord]
</script>
如果直接監(jiān)聽div元素,那么是不生效的:
<!-- 一個(gè)性感的div -->
<div id="box">Hello</div>
<script type="text/javascript">
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords)
);
// 監(jiān)聽div不會(huì)生效
let box = document.getElementById("box");
observer.observe(box, {
characterData: true
});
box.textContent = "Hi";
// 控制臺(tái)無(wú)輸出
</script>
characterDataOldValue記錄舊值:
如果要記錄文本舊值,可以設(shè)置 characterDataOldValue 為true。
<!-- 一個(gè)性感的div -->
<div id="box">Hello</div>
<script type="text/javascript">
let observer = new MutationObserver(
(mutationRecords) => console.log(mutationRecords.map((x) => x.oldValue))
);
// 獲取文本節(jié)點(diǎn)
let textNode = document.getElementById("box").childNodes[0];
observer.observe(textNode, {
// 觀察文本變化
characterData: true,
// 保留舊數(shù)據(jù)
characterDataOldValue: true,
});
// 修改文本兩次
textNode.textContent = "Hi";
textNode.textContent = "Nice";
// 控制臺(tái)打?。?
// (2) ['Hello', 'Hi']
</script>
因?yàn)閐iv內(nèi)的內(nèi)容原本為Hello,先修改為Hi,又修改為Nice,所以兩次修改的舊值就為:Hello 和 Hi 了。
3. 觀察子節(jié)點(diǎn)
MutationObserver 實(shí)例也可以觀察目標(biāo)節(jié)點(diǎn)子節(jié)點(diǎn)的變化。
<!-- 一個(gè)性感的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é)點(diǎn)變化
childList: true,
});
// 添加元素
let span = document.createElement("span")
span.textContent = "world";
box.appendChild(span);
// 控制臺(tái)打?。?
// [MutationRecord]
</script>
MutationRecord中的addedNodes屬性記錄了增加的節(jié)點(diǎn)。
移除節(jié)點(diǎn):
<!-- 一個(gè)性感的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é)點(diǎn)變化
childList: true,
});
// 移除第一個(gè)子節(jié)點(diǎn),就是Hello文本節(jié)點(diǎn)
box.removeChild(box.childNodes[0]);
// 控制臺(tái)打?。?
// [MutationRecord]
</script>
MutationRecord中的removedNodes屬性記錄了移除的節(jié)點(diǎn)。
移動(dòng)節(jié)點(diǎn):
對(duì)于已有的節(jié)點(diǎn)進(jìn)行移動(dòng),那么會(huì)記錄兩條MutationRecord記錄,因?yàn)橐苿?dòng)現(xiàn)有的節(jié)點(diǎn)是先刪除,后添加。
<!-- 一個(gè)性感的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é)點(diǎn)變化
childList: true,
});
// 將span節(jié)點(diǎn)移動(dòng)到Hello節(jié)點(diǎn)前面
box.insertBefore(box.childNodes[1], box.childNodes[0]);
// 移動(dòng)節(jié)點(diǎn),實(shí)際是先刪除,后添加。
// 控制臺(tái)打印:
// (2) [MutationRecord, MutationRecord]
</script>
4. 觀察子樹
上面觀察的節(jié)點(diǎn)都是當(dāng)前設(shè)置的目標(biāo)節(jié)點(diǎn),比如body,就只能觀察body元素和其子節(jié)點(diǎn)的變化。
如果要觀察body及其所有后代節(jié)點(diǎn)的變化,那么可以設(shè)置subtree屬性為true。
<!-- 一個(gè)性感的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";
// 控制臺(tái)打?。?
// [MutationRecord]
</script>
subtree設(shè)置為true后,不光div元素本身,span元素也可以觀察到了。
總結(jié):
- 1.
MutationObserver實(shí)例可以用來(lái)觀察對(duì)象。 - 2.
MutationRecord實(shí)例記錄了每一次的變化。 - 3. 回調(diào)函數(shù)需要所有腳本任務(wù)完成后,才會(huì)執(zhí)行,即采用異步方式。
- 4. 可以觀察的訪問(wèn)有屬性、文本、子節(jié)點(diǎn)、子樹。
到此這篇關(guān)于JavaScript中MutationObServer監(jiān)聽DOM元素詳情的文章就介紹到這了,更多相關(guān)JavaScript中MutationObServer監(jiān)聽DOM元素內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS圖形編輯器場(chǎng)景坐標(biāo)視口坐標(biāo)的相互轉(zhuǎn)換
這篇文章主要為大家介紹了JS圖形編輯器之場(chǎng)景坐標(biāo)視口坐標(biāo)的相互轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
javascript實(shí)現(xiàn)字典Dictionary示例基礎(chǔ)
這篇文章主要為大家介紹了javascript實(shí)現(xiàn)字典Dictionary基礎(chǔ)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Nest.js 之依賴注入原理及實(shí)現(xiàn)過(guò)程詳解
這篇文章主要為大家介紹了Nest.js 之依賴注入原理及實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

