<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你不知道的 DOM 變動(dòng)觀察器:Mutation observer

          共 7953字,需瀏覽 16分鐘

           ·

          2021-04-25 11:10

          DOM 變動(dòng)觀察器(Mutation observer)

          MutationObserver 是一個(gè)內(nèi)建對(duì)象,它觀察 DOM 元素,并在檢測(cè)到更改時(shí)觸發(fā)回調(diào)。

          我們將首先看一下語(yǔ)法,然后探究一個(gè)實(shí)際的用例,以了解它在什么地方有用。

          語(yǔ)法

          MutationObserver 使用簡(jiǎn)單。

          首先,我們創(chuàng)建一個(gè)帶有回調(diào)函數(shù)的觀察器:

          let observer = new MutationObserver(callback);

          然后將其附加到一個(gè) DOM 節(jié)點(diǎn):

          observer.observe(node, config);

          config 是一個(gè)具有布爾選項(xiàng)的對(duì)象,該布爾選項(xiàng)表示“將對(duì)哪些更改做出反應(yīng)”:

          • childList —— node 的直接子節(jié)點(diǎn)的更改,
          • subtree —— node 的所有后代的更改,
          • attributes —— node 的特性(attribute),
          • attributeFilter —— 特性名稱(chēng)數(shù)組,只觀察選定的特性。
          • characterData —— 是否觀察 node.data(文本內(nèi)容),

          其他幾個(gè)選項(xiàng):

          • attributeOldValue —— 如果為 true,則將特性的舊值和新值都傳遞給回調(diào)(參見(jiàn)下文),否則只傳新值(需要 attributes 選項(xiàng)),
          • characterDataOldValue —— 如果為 true,則將 node.data 的舊值和新值都傳遞給回調(diào)(參見(jiàn)下文),否則只傳新值(需要 characterData 選項(xiàng))。

          然后,在發(fā)生任何更改后,將執(zhí)行“回調(diào)”:更改被作為一個(gè) MutationRecord[1] 對(duì)象列表傳入第一個(gè)參數(shù),而觀察器自身作為第二個(gè)參數(shù)。

          MutationRecord[2] 對(duì)象具有以下屬性:

          • type —— 變動(dòng)類(lèi)型,以下類(lèi)型之一:
            • "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 —— 被更改的特性的名稱(chēng)/命名空間(用于 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, {
            childListtrue// 觀察直接子節(jié)點(diǎn)
            subtreetrue// 及其更低的后代節(jié)點(diǎn)
            characterDataOldValuetrue // 將舊的數(shù)據(jù)傳遞給回調(diào)
          });
          </script>

          如果我們?cè)跒g覽器中運(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ì)信息取決于瀏覽器如何處理此類(lèi)刪除
            // 它可能是將兩個(gè)相鄰的文本節(jié)點(diǎn) "edit " 和 ", please" 合并成一個(gè)節(jié)點(diǎn),
            // 或者可能將它們留在單獨(dú)的文本節(jié)點(diǎn)中
          }];

          因此,MutationObserver 允許對(duì) DOM 子樹(shù)中的任何更改作出反應(yīng)。

          用于集成

          在什么時(shí)候可能有用?

          想象一下,你需要添加一個(gè)第三方腳本,該腳本不僅包含有用的功能,還會(huì)執(zhí)行一些我們不想要的操作,例如顯示廣告 <div class="ads">Unwanted ads</div>

          當(dāng)然,第三方腳本沒(méi)有提供刪除它的機(jī)制。

          使用 MutationObserver,我們可以監(jiān)測(cè)到我們不需要的元素何時(shí)出現(xiàn)在我們的 DOM 中,并將其刪除。

          還有一些其他情況,例如第三方腳本會(huì)將某些內(nèi)容添加到我們的文檔中,并且我們希望檢測(cè)出這種情況何時(shí)發(fā)生,以調(diào)整頁(yè)面,動(dòng)態(tài)調(diào)整某些內(nèi)容的大小等。

          MutationObserver 使我們能夠?qū)崿F(xiàn)這種需求。

          用于架構(gòu)

          從架構(gòu)的角度來(lái)看,在某些情況下,MutationObserver 有不錯(cuò)的作用。

          假設(shè)我們正在建立一個(gè)有關(guān)編程的網(wǎng)站。自然地,文章和其他材料中可能包含源代碼段。

          在 HTML 標(biāo)記(markup)中的此類(lèi)片段如下所示:

          ...
          <pre class="language-javascript"><code>
            // 這里是代碼
            let hello = "world";
          </code></pre>
          ...

          為了提高可讀性,同時(shí)對(duì)其進(jìn)行美化,我們將在我們的網(wǎng)站上使用 JavaScript 語(yǔ)法高亮顯示庫(kù),例如 Prism.js[3]。為了使用 Prism 對(duì)以上代碼片段進(jìn)行語(yǔ)法高亮顯示,我們調(diào)用了 Prism.highlightElem(pre),它會(huì)檢查此類(lèi) pre 元素的內(nèi)容,并為這些元素添加特殊的標(biāo)簽(tag)和樣式,以進(jìn)行彩色語(yǔ)法高亮顯示,類(lèi)似于你在本文的示例中看到的那樣。

          那么,我們應(yīng)該在什么時(shí)候執(zhí)行該高亮顯示方法呢?我們可以在 DOMContentLoaded 事件中執(zhí)行,或者將腳本放在頁(yè)面的底部。DOM 就緒后,我們可以搜索元素 pre[class*="language"] 并對(duì)其調(diào)用 Prism.highlightElem

          // 高亮顯示頁(yè)面上的所有代碼段
          document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);

          到目前為止,一切都很簡(jiǎn)單,對(duì)吧?我們找到 HTML 中的代碼片段并高亮顯示它們。

          現(xiàn)在讓我們繼續(xù)。假設(shè)我們要從服務(wù)器動(dòng)態(tài)獲取資料。我們將 在本教程的后續(xù)章節(jié)[4] 中學(xué)習(xí)進(jìn)行此操作的方法。目前,只需要關(guān)心我們從網(wǎng)絡(luò)服務(wù)器獲取 HTML 文章并按需顯示:

          let article = /* 從服務(wù)器獲取新內(nèi)容 */
          articleElem.innerHTML = article;

          新的 article HTML 可能包含代碼段。我們需要對(duì)其調(diào)用 Prism.highlightElem,否則它們將不會(huì)被高亮顯示。

          對(duì)于動(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)容:文章,測(cè)驗(yàn)和論壇帖子等。我們是否需要在每個(gè)地方都附加一個(gè)高亮顯示調(diào)用,以在內(nèi)容加載完成后,高亮內(nèi)容中的代碼。那很不方便。

          并且,如果內(nèi)容是由第三方模塊加載的,該怎么辦?例如,我們有一個(gè)由其他人編寫(xiě)的論壇,該論壇可以動(dòng)態(tài)加載內(nèi)容,并且我們想為其添加語(yǔ)法高亮顯示。沒(méi)有人喜歡修補(bǔ)第三方腳本。

          幸運(yùn)的是,還有另一種選擇。

          我們可以使用 MutationObserver 來(lái)自動(dòng)檢測(cè)何時(shí)在頁(yè)面中插入了代碼段,并高亮顯示它們。

          因此,我們?cè)谝粋€(gè)地方處理高亮顯示功能,從而使我們無(wú)需集成它。

          動(dòng)態(tài)高亮顯示示例

          這是一個(gè)工作示例。

          如果你運(yùn)行這段代碼,它將開(kāi)始觀察下面的元素,并高亮顯示現(xiàn)在此處的所有代碼段:

          let observer = new MutationObserver(mutations => {

            for(let mutation of mutations) {
              // 檢查新節(jié)點(diǎn),有什么需要高亮顯示的嗎?

              for(let node of mutation.addedNodes) {
                // 我們只跟蹤元素,跳過(guò)其他節(jié)點(diǎn)(例如文本節(jié)點(diǎn))
                if (!(node instanceof HTMLElement)) continue;

                // 檢查插入的元素是否為代碼段
                if (node.matches('pre[class*="language-"]')) {
                  Prism.highlightElement(node);
                }

                // 或者可能在子樹(shù)的某個(gè)地方有一個(gè)代碼段?
                for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
                  Prism.highlightElement(elem);
                }
              }
            }

          });

          let demoElem = document.getElementById('highlight-demo');

          observer.observe(demoElem, {childListtruesubtreetrue});

          下面有一個(gè) HTML 元素,以及使用 innerHTML 動(dòng)態(tài)填充它的 JavaScript。

          請(qǐng)先運(yùn)行前面那段代碼(上面那段,觀察元素),然后運(yùn)行下面這段代碼。你將看到 MutationObserver 是如何檢測(cè)并高亮顯示代碼段的。

          <p id="highlight-demo" style="border: 1px solid #ddd">一個(gè)具有 <code>id="highlight-demo"</code> 的示例元素,運(yùn)行上面那段代碼來(lái)觀察它。</p>

          下面這段代碼填充了其 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 中添加/刪除代碼段,而無(wú)需考慮高亮問(wèn)題。

          其他方法

          有一個(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)部對(duì)節(jié)點(diǎn)使用弱引用。也就是說(shuō),如果一個(gè)節(jié)點(diǎn)被從 DOM 中移除了,并且該節(jié)點(diǎn)變得不可訪(fǎng)問(wèn),那么它就可以被垃圾回收。

          觀察到 DOM 節(jié)點(diǎn)這一事實(shí)并不能阻止垃圾回收。

          總結(jié)

          MutationObserver 可以對(duì) DOM 的變化作出反應(yīng) —— 特性(attribute),文本內(nèi)容,添加/刪除元素。

          我們可以用它來(lái)跟蹤代碼其他部分引入的更改,以及與第三方腳本集成。

          MutationObserver 可以跟蹤任何更改。config “要觀察的內(nèi)容”選項(xiàng)用于優(yōu)化,避免不必要的回調(diào)調(diào)用以節(jié)省資源。


          現(xiàn)代 JavaScript 教程:開(kāi)源的現(xiàn)代 JavaScript 從入門(mén)到進(jìn)階的優(yōu)質(zhì)教程。React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程[5]

          在線(xiàn)免費(fèi)閱讀:https://zh.javascript.info


          參考資料

          [1]

          MutationRecord: https://dom.spec.whatwg.org/#mutationrecord

          [2]

          MutationRecord: https://dom.spec.whatwg.org/#mutationrecord

          [3]

          Prism.js: https://prismjs.com/

          [4]

          在本教程的后續(xù)章節(jié): https://zh.javascript.info/fetch

          [5]

          React 官方文檔推薦,與 MDN 并列的 JavaScript 學(xué)習(xí)教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources


          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  加勒比在线综合 | 成人免费视频 国产免费观看 | 亚洲精品成人无码在线观看 | 人人人干干人人 | 久草视频免费在线观看 |