W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
?MutationObserver
? 是一個(gè)內(nèi)建對象,它觀察 DOM 元素,并在檢測到更改時(shí)觸發(fā)回調(diào)。
我們將首先看一下語法,然后探究一個(gè)實(shí)際的用例,以了解它在什么地方有用。
MutationObserver
使用簡單。
首先,我們創(chuàng)建一個(gè)帶有回調(diào)函數(shù)的觀察器:
let observer = new MutationObserver(callback);
然后將其附加到一個(gè) DOM 節(jié)點(diǎn):
observer.observe(node, config);
config
是一個(gè)具有布爾選項(xiàng)的對象,該布爾選項(xiàng)表示“將對哪些更改做出反應(yīng)”:
childList
? —— ?node
? 的直接子節(jié)點(diǎn)的更改,subtree
? —— ?node
? 的所有后代的更改,attributes
? —— ?node
? 的特性(attribute),attributeFilter
? —— 特性名稱數(shù)組,只觀察選定的特性。characterData
? —— 是否觀察 ?node.data
?(文本內(nèi)容),其他幾個(gè)選項(xiàng):
attributeOldValue
? —— 如果為 ?true
?,則將特性的舊值和新值都傳遞給回調(diào)(參見下文),否則只傳新值(需要 ?attributes
? 選項(xiàng)),characterDataOldValue
? —— 如果為 ?true
?,則將 ?node.data
? 的舊值和新值都傳遞給回調(diào)(參見下文),否則只傳新值(需要 ?characterData
? 選項(xiàng))。然后,在發(fā)生任何更改后,將執(zhí)行“回調(diào)”:更改被作為一個(gè) MutationRecord 對象列表傳入第一個(gè)參數(shù),而觀察器自身作為第二個(gè)參數(shù)。
MutationRecord 對象具有以下屬性:
type
? —— 變動(dòng)類型,以下類型之一:"attributes"
?:特性被修改了,"characterData"
?:數(shù)據(jù)被修改了,用于文本節(jié)點(diǎn),"childList"
?:添加/刪除了子元素。target
? —— 更改發(fā)生在何處:?"attributes"
? 所在的元素,或 ?"characterData"
? 所在的文本節(jié)點(diǎn),或 ?"childList"
? 變動(dòng)所在的元素,addedNodes/removedNodes
? —— 添加/刪除的節(jié)點(diǎn),previousSibling/nextSibling
? —— 添加/刪除的節(jié)點(diǎn)的上一個(gè)/下一個(gè)兄弟節(jié)點(diǎn),attributeName/attributeNamespace
? —— 被更改的特性的名稱/命名空間(用于 XML),oldValue
? —— 之前的值,僅適用于特性或文本更改,如果設(shè)置了相應(yīng)選項(xiàng) ?attributeOldValue/characterDataOldValue
?。例如,這里有一個(gè) <div>
,它具有 contentEditable
特性。該特性使我們可以聚焦和編輯元素。
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
<script>
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(the changes)
});
// 觀察除了特性之外的所有變動(dòng)
observer.observe(elem, {
childList: true, // 觀察直接子節(jié)點(diǎn)
subtree: true, // 及其更低的后代節(jié)點(diǎn)
characterDataOldValue: true // 將舊的數(shù)據(jù)傳遞給回調(diào)
});
</script>
如果我們在瀏覽器中運(yùn)行上面這段代碼,并聚焦到給定的 <div>
上,然后更改 <b>edit</b>
中的文本,console.log
將顯示一個(gè)變動(dòng):
mutationRecords = [{
type: "characterData",
oldValue: "edit",
target: <text node>,
// 其他屬性為空
}];
如果我們進(jìn)行更復(fù)雜的編輯操作,例如刪除 <b>edit</b>
,那么變動(dòng)事件可能會(huì)包含多個(gè)變動(dòng)記錄:
mutationRecords = [{
type: "childList",
target: <div#elem>,
removedNodes: [<b>],
nextSibling: <text node>,
previousSibling: <text node>
// 其他屬性為空
}, {
type: "characterData"
target: <text node>
// ...變動(dòng)的詳細(xì)信息取決于瀏覽器如何處理此類刪除
// 它可能是將兩個(gè)相鄰的文本節(jié)點(diǎn) "edit " 和 ", please" 合并成一個(gè)節(jié)點(diǎn),
// 或者可能將它們留在單獨(dú)的文本節(jié)點(diǎn)中
}];
因此,MutationObserver
允許對 DOM 子樹中的任何更改作出反應(yīng)。
在什么時(shí)候可能有用?
想象一下,你需要添加一個(gè)第三方腳本,該腳本不僅包含有用的功能,還會(huì)執(zhí)行一些我們不想要的操作,例如顯示廣告 <div class="ads">Unwanted ads</div>
。
當(dāng)然,第三方腳本沒有提供刪除它的機(jī)制。
使用 MutationObserver
,我們可以監(jiān)測到我們不需要的元素何時(shí)出現(xiàn)在我們的 DOM 中,并將其刪除。
還有一些其他情況,例如第三方腳本會(huì)將某些內(nèi)容添加到我們的文檔中,并且我們希望檢測出這種情況何時(shí)發(fā)生,以調(diào)整頁面,動(dòng)態(tài)調(diào)整某些內(nèi)容的大小等。
MutationObserver
使我們能夠?qū)崿F(xiàn)這種需求。
從架構(gòu)的角度來看,在某些情況下,MutationObserver
有不錯(cuò)的作用。
假設(shè)我們正在建立一個(gè)有關(guān)編程的網(wǎng)站。自然地,文章和其他材料中可能包含源代碼段。
在 HTML 標(biāo)記(markup)中的此類片段如下所示:
...
<pre class="language-javascript"><code>
// 這里是代碼
let hello = "world";
</code></pre>
...
為了提高可讀性,同時(shí)對其進(jìn)行美化,我們將在我們的網(wǎng)站上使用 JavaScript 語法高亮顯示庫,例如 Prism.js。為了使用 Prism 對以上代碼片段進(jìn)行語法高亮顯示,我們調(diào)用了 Prism.highlightElem(pre)
,它會(huì)檢查此類 pre
元素的內(nèi)容,并為這些元素添加特殊的標(biāo)簽(tag)和樣式,以進(jìn)行彩色語法高亮顯示,類似于你在本文的示例中看到的那樣。
那么,我們應(yīng)該在什么時(shí)候執(zhí)行該高亮顯示方法呢?我們可以在 DOMContentLoaded
事件中執(zhí)行,或者將腳本放在頁面的底部。DOM 就緒后,我們可以搜索元素 pre[class*="language"]
并對其調(diào)用 Prism.highlightElem
:
// 高亮顯示頁面上的所有代碼段
document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);
到目前為止,一切都很簡單,對吧?我們找到 HTML 中的代碼片段并高亮顯示它們。
現(xiàn)在讓我們繼續(xù)。假設(shè)我們要從服務(wù)器動(dòng)態(tài)獲取資料。我們將 在本教程的后續(xù)章節(jié) 中學(xué)習(xí)進(jìn)行此操作的方法。目前,只需要關(guān)心我們從網(wǎng)絡(luò)服務(wù)器獲取 HTML 文章并按需顯示:
let article = /* 從服務(wù)器獲取新內(nèi)容 */
articleElem.innerHTML = article;
新的 article
HTML 可能包含代碼段。我們需要對其調(diào)用 Prism.highlightElem
,否則它們將不會(huì)被高亮顯示。
對于動(dòng)態(tài)加載的文章,應(yīng)該在何處何時(shí)調(diào)用 Prism.highlightElem
?
我們可以將該調(diào)用附加到加載文章的代碼中,如下所示:
let article = /* 從服務(wù)器獲取新內(nèi)容 */
articleElem.innerHTML = article;
let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
snippets.forEach(Prism.highlightElem);
……但是,想象一下,如果代碼中有很多地方都是在加載內(nèi)容:文章,測驗(yàn)和論壇帖子等。我們是否需要在每個(gè)地方都附加一個(gè)高亮顯示調(diào)用,以在內(nèi)容加載完成后,高亮內(nèi)容中的代碼。那很不方便。
并且,如果內(nèi)容是由第三方模塊加載的,該怎么辦?例如,我們有一個(gè)由其他人編寫的論壇,該論壇可以動(dòng)態(tài)加載內(nèi)容,并且我們想為其添加語法高亮顯示。沒有人喜歡修補(bǔ)第三方腳本。
幸運(yùn)的是,還有另一種選擇。
我們可以使用 MutationObserver
來自動(dòng)檢測何時(shí)在頁面中插入了代碼段,并高亮顯示它們。
因此,我們在一個(gè)地方處理高亮顯示功能,從而使我們無需集成它。
這是一個(gè)工作示例。
如果你運(yùn)行這段代碼,它將開始觀察下面的元素,并高亮顯示現(xiàn)在此處的所有代碼段:
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
// 檢查新節(jié)點(diǎn),有什么需要高亮顯示的嗎?
for(let node of mutation.addedNodes) {
// 我們只跟蹤元素,跳過其他節(jié)點(diǎn)(例如文本節(jié)點(diǎn))
if (!(node instanceof HTMLElement)) continue;
// 檢查插入的元素是否為代碼段
if (node.matches('pre[class*="language-"]')) {
Prism.highlightElement(node);
}
// 或者可能在子樹的某個(gè)地方有一個(gè)代碼段?
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
Prism.highlightElement(elem);
}
}
}
});
let demoElem = document.getElementById('highlight-demo');
observer.observe(demoElem, {childList: true, subtree: true});
下面有一個(gè) HTML 元素,以及使用 innerHTML
動(dòng)態(tài)填充它的 JavaScript。
請先運(yùn)行前面那段代碼(上面那段,觀察元素),然后運(yùn)行下面這段代碼。你將看到 MutationObserver
是如何檢測并高亮顯示代碼段的。
下面這段代碼填充了其 innerHTML
,這導(dǎo)致 MutationObserver
作出反應(yīng),并突出顯示其內(nèi)容:
let demoElem = document.getElementById('highlight-demo');
// 動(dòng)態(tài)插入帶有代碼段的內(nèi)容
demoElem.innerHTML = `下面是一個(gè)代碼段:
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
<div>另一個(gè)代碼段:</div>
<div>
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
</div>
`;
現(xiàn)在我們有了 MutationObserver
,它可以跟蹤觀察到的元素中的,或者整個(gè) document
中的所有高亮顯示。我們可以在 HTML 中添加/刪除代碼段,而無需考慮高亮問題。
有一個(gè)方法可以停止觀察節(jié)點(diǎn):
observer.disconnect()
? —— 停止觀察。當(dāng)我們停止觀察時(shí),觀察器可能尚未處理某些更改。在種情況下,我們使用:
observer.takeRecords()
? —— 獲取尚未處理的變動(dòng)記錄列表,表中記錄的是已經(jīng)發(fā)生,但回調(diào)暫未處理的變動(dòng)。這些方法可以一起使用,如下所示:
// 如果你關(guān)心可能未處理的近期的變動(dòng)
// 那么,應(yīng)該在 disconnect 前調(diào)用獲取未處理的變動(dòng)列表
let mutationRecords = observer.takeRecords();
// 停止跟蹤變動(dòng)
observer.disconnect();
...
?
observer.takeRecords()
? 返回的記錄被從處理隊(duì)列中移除回調(diào)函數(shù)不會(huì)被
observer.takeRecords()
返回的記錄調(diào)用。
垃圾回收
觀察器在內(nèi)部對節(jié)點(diǎn)使用弱引用。也就是說,如果一個(gè)節(jié)點(diǎn)被從 DOM 中移除了,并且該節(jié)點(diǎn)變得不可訪問,那么它就可以被垃圾回收。
觀察到 DOM 節(jié)點(diǎn)這一事實(shí)并不能阻止垃圾回收。
MutationObserver
可以對 DOM 的變化作出反應(yīng) —— 特性(attribute),文本內(nèi)容,添加/刪除元素。
我們可以用它來跟蹤代碼其他部分引入的更改,以及與第三方腳本集成。
MutationObserver
可以跟蹤任何更改。config
“要觀察的內(nèi)容”選項(xiàng)用于優(yōu)化,避免不必要的回調(diào)調(diào)用以節(jié)省資源。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: