# 探索 Browser API(中)
今天會介紹 MutationObserver
與 IntersectionObserver
這兩個最近常見的 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 相對起來比較新的關係,有時候如果要相容其他瀏覽器反而綁手綁腳的。