MutationObserver - DOM 宇宙的觀察者

alexianalexian
2022-11-19
JavaScriptMutationObserverobserver pattern

你不看我怎麼知道我看你?

網頁前端開發與日俱進,不斷有新的 web API 出現,為前端開發帶來更多不同的可能,今天要為大家介紹的是 MutationObserver (MDN)。

MutationObserver 可以在不相依其他監聽事件的情況下,獨立觀察元素內的節點、屬性及文字內容的變化。它有一個前身:Mutation Event,但由於效能等因素被棄用,到現在以觀察者模式(Observer pattern)重新被實踐,接下來我們來看看要怎麼的使用 MutationObserver 吧!

MutationObserver 的使用方式

1. 建構 MutationObserver 實體

MutationObserver 作為一建構子,需要實體化,並傳入一個回傳函式,其函式接受一組包含 mutationRecord (MDN) 的陣列,根據 mutationRecord 的內容,我們可以取得 DOM 已完成的更動,諸如 DOM 的屬性(attributes)或子節點(childList)的變化,以及變化前後的值,都可以從 mutationRecord取得。

const observer = new MutationObserver((mutationRecords) => {
    mutationRecords.forEach((mutationRecord) => {
    switch (mutationRecord.type) {
            case 'attributes':
                console.log('元素屬性變化');
                break;
            case 'characterData':
                console.log('文字內容屬性變化');
                break;
            case 'childList':
                console.log('子節點變化';
                break;
    }
  })
};

不知道大家會不會疑惑,為什麼觀察到的變化會是一個陣列呢?那是因為 MutationObserver 的行為是非同步的,待目前所有對 DOM 的變更完成後,才能觸發,藉此避免大量的效能浪費。

2. MutationObserver 實體執行 observe(element, options)

options 有以下的屬性可以使用:

1. 偵測更動的類型(必須擇一開啟):

  • attributes (Boolean) 偵測屬性的更動
  • childList (Boolean)偵測子節點的更動
  • charactorData (Boolean)偵測字元內容更動

2. 偵測更動的範圍:

  • subtree(Boolean)對其節點下所有延伸的節點進行觀察
  • attributeFilter (String[])過濾偵測更動的屬性,需要 attributes 設為 ture

3. 捕抓更動前的狀態:

  • attributeOldValue (Boolean)屬性更動前的值
  • characterDataOldValue (Boolean)字元內容更動前的值
observer.observe(document.body, {
  attributes: true, // 偵測屬性的更動
    subtree: true, // 對其節點下所有延伸的節點進行觀察
})

以下是簡單的 playground,可以勾選不同的 option 測試看看:

MutationObserver 使用時機與實際應用

講解完使用方法,那究竟在什麼實際情況下,會使用上 MutationObserver 呢?而我目前使用到 MutationObserver 的時機,主要是以下兩個:

1. 製作第三方工具

假如你需要製作第三方的工具,例如製作 Chrome extension 時,需要偵測畫面有新元素的加入時,為加入內容進行擴充,這時候便是 MutationObserver 發揮所長的時候。

這邊舉個簡單的例子,假如我想為 YouTube 首頁每一個影片縮圖加入提示,假如我在載入頁面後馬上執行,雖然目前的內容有加入成功,但延遲載入加載的內容則無法套用 (為演示方便直接 console 執行):

使用 mutationObserver 前:

只有目前顯示的影片有效,延遲載入新增的內容不會被影響

這時候便可以使用 MutationObserver 偵測頁面的內容是否有變動,並在變動時對新增的內容(這次例子是影片縮圖)進行操作:

使用 mutationObserver 後:

延遲載入的影片也有綠色點點了

範例程式碼:

const observer = new MutationObserver((mutations) => {
  const imageAdded = mutations.some((mutation) => {
    return mutation.type === 'childList' && mutation.addedNodes.length > 0;
  });

  if (imageAdded) {
    const imageBlocks = document.querySelectorAll('yt-image');

    observer.disconnect();

    imageBlocks.forEach((imageBlock) => {
      if (!imageBlock.dataset.added) {
        imageBlock.after(createLabel());
        imageBlock.dataset.added = true;
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  }
});

function createLabel() {
  const label = document.createElement('div');
  const styles = {
    position: 'absolute',
    left: '5px',
    top: '5px',
    backgroundColor: '#22ff44',
    width: '20px',
    height: '20px',
    borderRadius: '20px',
  };
  Object.assign(label.style, styles);

  return label;
}

observer.observe(document.body, {
  childList: true,
  subtree: true,
});

2. 內容編輯器

前端的大家應該對元素的 contenteditable 屬性並不陌生,對於取得因 contenteditable 而變更的內容,通常會使用 input 事件 ,但如果要以 contenteditable 為基礎製作內容編輯器時,其元素內會有多層的元素結構,當要比對更新的內容時會非常不便;這時也可以考慮使用 MutationObserver 進行實作,便可以快速找到是哪一個元素的內容觸發更新:

MutationObserver 的注意事項

作為觀察者模式 API,MutationObserver 給予我們監聽元素內容變動的機制,便於對第三方內容進行後處理;在團隊的應用上,也可以做出如 CustomEvent 有助減少程式藕合的開發體驗。

另外,對於進行觀察的元素,應該儘可能把範圍限縮,如上面第一個實作的範例,就可以從 document.body 限縮至右側的主內容 #content,對於執行 oberver() 時,也可以限縮觀察的選項,減少不必要的 callback,以利節省效能。

而在瀏覽器相容性方面,連被停止支援(放逐)的 IE 11 也適用,所以可以放心使用:

mutationObserver

經過以上介紹,希望能讓大家能多認識 MutationObserver 這個 API ,並活用於前端開發上喔。

參考

  • MDN MutationObserver
  • https://stackoverflow.com/questions/14564617/when-are-mutationobserver-callbacks-fired

上一篇

Gatsby 部落格更新記錄 1.0

下一篇

Raycast — Mac 提升工作效率的啟動工具


Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發

Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發


共29篇文章
文章分類
  • 前端開發14
  • JavaScript7
  • 那些前端開發應該要知道的小事5
  • Gatsby.js3
  • ChromeExtension2
  • NetlifyCMS2
  • 生產力工具2
  • 關於我1
  • 非Coding1
  • Svelte1
  • raycast1
  • toolRecommend1
  • productivity1
  • MutationObserver1
  • observer pattern1
  • 開發效率1
  • will-change1
  • GitHubCopilot1
  • 生產力1
  • ChatGPT1
  • AI1
  • Copilot1
  • WebAssembly1
  • SEO1
  • GoogleSearchConsole1
  • react1
  • applescript1
  • 閱讀1
  • css1
  • grid1
  • micro-frontend1

Build with GatsbyJS and React 18.2.0. Hosted on Netlify.

The original code is open source and available at calpa/gatsby-starter-calpa-blog

Copyright ©AlexIan's blog 2025.

  1. 從頭開始上架 Chrome extension

    上回提到我們在本地端建好一個開發版本的 Chrome …
  2. Gatsby 部落格更新記錄 1.0

    首先把 Blog 連結貼在最前面:https://alex-ian.me/ 許多工程師 …
  3. Debounce & Throttle - 那些前端開發應該要知道的小事(一)

    前言 也許一開始接觸前端開發的新手們,都有使 …
  4. 使用 GitHub Copilot 一個月心得

    後悔自己為何沒有早點課金 在 Modern Web Conference 2023 觀後感 …
  5. 什麼是 Headless CMS?以探索 Netlify CMS 為例

    小弟的部落格使用 Gatsby.js 框架來架設,作為一個靜 …
  6. Modern Web Conference 2023 觀後感

    https://modernweb.tw/ 前言 Modern Web Conference (MWC) 作為每年度經典的網頁開 …
  7. 什麼是語法糖(Syntactic sugar)? - 2024 你要知道的 JS 語法糖 🍭

    語法糖能吃嗎? 在算數學時,不知道你什麼時候開 …
  8. 閉包 - 那些前端開發應該要知道的小事(三)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  9. Raycast — Mac 提升工作效率的啟動工具

    Mac 的使用者,應該對 Spotlight 這個系統預設的啟動工具 …
  10. 使用 Notion 管理我的年度目標 暨 2023 回顧 & 2024 展望

    都進入 2024 才來展望 想要成為流量的奴隸,自然要 …
  11. 慢半拍來初嚐 Svelte

    Svelte 都出來兩年多的框架現在才來玩,有夠慢半拍 …
  12. 從頭開始學習開發 Chrome extension (v3 版本)

    Chrome extension 經歷過 v1, v2 後,來到了 v3,雖然曾小量接觸 v2 …
  13. 留言簿

    歡迎留言
  14. addEventListener的第三個參數 - 那些前端開發應該要知道的小事(二)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  15. 在中大型企業下微前端的實現與挑戰

    我們先聊工程面,人的部分先不要 小弟任職前端 …
  16. will-change — 那些前端開發應該要知道的小事(七)

    你要做動畫你要先說啊 前端開發少不免會需要 …
  17. 關於我

    關於我 我是Alex Ian,是個小小的前端工程師,出來工 …
  18. 開發效率提升 - 操作鍵盤 & 快捷鍵篇

    我國中的電腦課,會教導學生倉頡和速成輸入法 …
  19. macbook-pro(retina) late2012 換電池記錄

    這篇文章其實與Coding無關,主要是記錄mbp換電池的 …
  20. polyfill是什麼?

    人在江湖,身不由己,在接案的過程中,總有一些你 …
所有結果