# 探索 Browser API(中)

今天會介紹 MutationObserverIntersectionObserver 這兩個最近常見的 API。

# MutationObserver

在實作 Virtual DOM,或是想要監聽 DOM 的變化,來改變對應的 model,MutationObserver 就是相當好用的函數。

MutationObserver 可以監聽 DOM 的變化,像是增刪、改變節點、Text node 變化等等。

透過宣告 callback 與 observe 函數,可以監聽 DOM node 裡頭的變化:

const mo = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
		// your code
  })
});

mo.observe(target);

裡頭的 mutation 會是一個 MutationRecord (opens new window),可以拿到 mutation 的變化、類型等等。同時你也會發現,我們傳入的 handler 拿到的 mutations 其實是個陣列,這是因為 MutationObserver 並不會同步執行,也就是每當 DOM 產生變化就直接執行,而是等到當前的操作結束後才觸發一次,對效能來說比較好,目前瀏覽器的支援也越來越好了。

另外,在使用上也要記得如果不用的話要呼叫 disconnect 這個方法來停止監聽。

# Vue 當中的 MutationObserver

在 vue 當中蠻有趣的實作是透過 MutationObserver 的方式來實作 nextTick。他的做法是這樣的:

var counter = 1;
var observer = new MutationObserver(nextTickHandler);
var textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
  characterData: true
})
timerFunc = () => {
  counter = (counter + 1) % 2
  textNode.data = String(counter)
}

透過 MutationObserver 是 micro task 的特性,可以確保頁面渲染與數據更新會在同一個 task 當中執行。

不過在 iOS WebView 中似乎會遇到 MutationObserver 無法運作的問題,在 Vue 上也有相關的 issue (opens new window)

# IntersectionObserver

IntersectionObserver 可以用來確認在 target 是否有進入當前的 viewport,在以往我們可能要用 scroll 來達到類似的效果,但監聽滾動事件就需要小心效能上的問題,尤其是如果用 getBoundingRect() 的話會觸發一次 layout,在 scroll event 裡面呼叫 getBoundingRect 聽起來就是抖抖的啊。

有了 IntersectionObserver 就可以很簡單地做到同樣的事。

什麼時候會想要使用 IntersectionObserver 呢?最常見的場景有:

  • 實作頁面無限滾動
  • 實作 Lazy loading
const io = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // do fetching
    }
  });
}, {
  root
  thresholds
});

io.observe(target);

一般的場景下,用 entry.isIntersecting 來判斷就已經足夠了,你也可以用 threshold 來決定怎樣的闕值算是可見。關於 threshold 的說明可以參考 MDN 的文件 (opens new window)

如果比較舊的瀏覽器沒有 IntersectionObserver 的話,可以考慮用 polyfill,或是搭配 webpack 做動態載入,當沒有 IntersectionObserver API 才動態引入 polyfill。

# 結論

隨著瀏覽器的 API 越來越豐富,我們可以更專注在功能開發上,而不是煩惱實作時各種細節與效能,不過也因為這些 API 相對起來比較新的關係,有時候如果要相容其他瀏覽器反而綁手綁腳的。