<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 變動觀察器:Mutation observer

          共 7953字,需瀏覽 16分鐘

           ·

          2021-03-09 20:52

          DOM 變動觀察器(Mutation observer)

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

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

          語法

          MutationObserver 使用簡單。

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

          let observer = new MutationObserver(callback);

          然后將其附加到一個 DOM 節(jié)點:

          observer.observe(node, config);

          config 是一個具有布爾選項的對象,該布爾選項表示“將對哪些更改做出反應”:

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

          其他幾個選項:

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

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

          MutationRecord[2] 對象具有以下屬性:

          • type —— 變動類型,以下類型之一:
            • "attributes":特性被修改了,
            • "characterData":數(shù)據(jù)被修改了,用于文本節(jié)點,
            • "childList":添加/刪除了子元素。
          • target —— 更改發(fā)生在何處:"attributes" 所在的元素,或 "characterData" 所在的文本節(jié)點,或 "childList" 變動所在的元素,
          • addedNodes/removedNodes —— 添加/刪除的節(jié)點,
          • previousSibling/nextSibling —— 添加/刪除的節(jié)點的上一個/下一個兄弟節(jié)點,
          • attributeName/attributeNamespace —— 被更改的特性的名稱/命名空間(用于 XML),
          • oldValue —— 之前的值,僅適用于特性或文本更改,如果設置了相應選項 attributeOldValue/characterDataOldValue

          例如,這里有一個 <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)
          });

          // 觀察除了特性之外的所有變動
          observer.observe(elem, {
            childListtrue// 觀察直接子節(jié)點
            subtreetrue// 及其更低的后代節(jié)點
            characterDataOldValuetrue // 將舊的數(shù)據(jù)傳遞給回調(diào)
          });
          </script>

          如果我們在瀏覽器中運行上面這段代碼,并聚焦到給定的 <div> 上,然后更改 <b>edit</b> 中的文本,console.log 將顯示一個變動:

          mutationRecords = [{
            type"characterData",
            oldValue"edit",
            target<text node>,
            // 其他屬性為空
          }];

          如果我們進行更復雜的編輯操作,例如刪除 <b>edit</b>,那么變動事件可能會包含多個變動記錄:

          mutationRecords = [{
            type"childList",
            target: <div#elem>,
            removedNodes: [<b>],
            nextSibling: <text node>,
            previousSibling: <text node>
            // 其他屬性為空
          }, {
            type: "characterData"
            target: <text node>
            // ...變動的詳細信息取決于瀏覽器如何處理此類刪除
            // 它可能是將兩個相鄰的文本節(jié)點 "edit " 和 ", please" 合并成一個節(jié)點,
            // 或者可能將它們留在單獨的文本節(jié)點中
          }];

          因此,MutationObserver 允許對 DOM 子樹中的任何更改作出反應。

          用于集成

          在什么時候可能有用?

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

          當然,第三方腳本沒有提供刪除它的機制。

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

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

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

          用于架構(gòu)

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

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

          在 HTML 標記(markup)中的此類片段如下所示:

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

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

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

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

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

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

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

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

          對于動態(tài)加載的文章,應該在何處何時調(diào)用 Prism.highlightElem

          我們可以將該調(diào)用附加到加載文章的代碼中,如下所示:

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

          let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
          snippets.forEach(Prism.highlightElem);

          ……但是,想象一下,如果代碼中有很多地方都是在加載內(nèi)容:文章,測驗和論壇帖子等。我們是否需要在每個地方都附加一個高亮顯示調(diào)用,以在內(nèi)容加載完成后,高亮內(nèi)容中的代碼。那很不方便。

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

          幸運的是,還有另一種選擇。

          我們可以使用 MutationObserver 來自動檢測何時在頁面中插入了代碼段,并高亮顯示它們。

          因此,我們在一個地方處理高亮顯示功能,從而使我們無需集成它。

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

          這是一個工作示例。

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

          let observer = new MutationObserver(mutations => {

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

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

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

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

          });

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

          observer.observe(demoElem, {childListtruesubtreetrue});

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

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

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

          下面這段代碼填充了其 innerHTML,這導致 MutationObserver 作出反應,并突出顯示其內(nèi)容:

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

          // 動態(tài)插入帶有代碼段的內(nèi)容
          demoElem.innerHTML = `下面是一個代碼段:
            <pre class="language-javascript"><code> let hello = "world!"; </code></pre>
            <div>另一個代碼段:</div>
            <div>
              <pre class="language-css"><code>.class { margin: 5px; } </code></pre>
            </div>
          `
          ;

          現(xiàn)在我們有了 MutationObserver,它可以跟蹤觀察到的元素中的,或者整個 document 中的所有高亮顯示。我們可以在 HTML 中添加/刪除代碼段,而無需考慮高亮問題。

          其他方法

          有一個方法可以停止觀察節(jié)點:

          • observer.disconnect() —— 停止觀察。

          當我們停止觀察時,觀察器可能尚未處理某些更改。在種情況下,我們使用:

          • observer.takeRecords() —— 獲取尚未處理的變動記錄列表,表中記錄的是已經(jīng)發(fā)生,但回調(diào)暫未處理的變動。

          這些方法可以一起使用,如下所示:

          // 如果你關(guān)心可能未處理的近期的變動
          // 那么,應該在 disconnect 前調(diào)用獲取未處理的變動列表
          let mutationRecords = observer.takeRecords();

          // 停止跟蹤變動
          observer.disconnect();
          ...

          observer.takeRecords() 返回的記錄被從處理隊列中移除:

          回調(diào)函數(shù)不會被 observer.takeRecords() 返回的記錄調(diào)用。

          垃圾回收:

          觀察器在內(nèi)部對節(jié)點使用弱引用。也就是說,如果一個節(jié)點被從 DOM 中移除了,并且該節(jié)點變得不可訪問,那么它就可以被垃圾回收。

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

          總結(jié)

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

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

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


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

          在線免費閱讀: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 學習教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产成人无码A片免费男男 | 久久精品男男 | 国产操逼片 | 久久丁香五月天综合网 | 无需播放器的AV |