[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

玉兒Qi發表於2019-03-04

JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

本系列專門研究 JavaScript 及其構建元件,這是第 10 期。在識別和描述核心元素的過程中,我們也分享了一些構建 SessionStack 的重要法則,SessionStack 是一個 JavaScript 應用,為了幫助使用者實時檢視和再現他們的 web 應用程式缺陷,它需要健壯並且高效能。

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

web 應用正在持續的越來越側重客戶端,這是由很多原因造成的,例如需要更豐富的 UI 來承載複雜應用的需求,實時運算,等等。

持續增加的複雜度使得在 web 應用的生命週期的任意時刻中獲取 UI 的確切狀態越來越困難。

而當你在搭建某些框架或者庫的時候,甚至會更加困難,例如,前者需要根據 DOM 來作出反應並執行特定的動作。

概覽

MutationObserver 是一個現代瀏覽器提供的 Web API,用於檢測 DOM 的變化。藉助這個 API,可以監聽到節點的新增或移除,節點的屬性變化或者字元節點的字元內容變化。

為什麼你會想要監聽 DOM?

這裡有很多 MutationObserver API 帶來極大便捷的例子,比如:

  • 你想要提醒 web 應用的使用者,他現在所瀏覽的頁面有內容發生了變化。
  • 你正在使用一個新的花哨的 JavaScript 框架,它根據 DOM 的變化動態載入 JavaScript 模組。
  • 也許你正在開發一個 WYSIWYG 編輯器,並試著實現撤銷/重做功能。藉助 MutationObserver API,你在任何時間都能知道發生了什麼變化,所以撤銷也就非常容易。
[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

這裡有幾個關於 MutationObserver 是如何帶來便捷的例子。

如何使用 MutationObserver

MutationObserver 應用於你的應用相當簡單。你需要通過傳入一個函式來建立一個 MutationObserver 例項,每當有變化發生,這個函式將會被呼叫。函式的第一個引數是一個批次內所有的變化(mutation)的集合。每個變化都會提供它的型別和已經發生的變化的資訊。

var mutationObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation);
  });
});
複製程式碼

這個被建立的物件有三個方法:

  • observe — 開始監聽變化。需要兩個引數 – 你需要觀察的 DOM 和一個設定物件
  • disconnect — 停止監聽變化
  • takeRecords — 在回撥函式呼叫之前,返回最後一個批次的變化。

下面這個程式碼片段展示瞭如何開始觀察:

// 開始監聽頁面中根 HTML 元素中的變化。
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});
複製程式碼

現在,假設在 DOM 中你有一些非常簡單的 div

<div id="sample-div" class="test"> Simple div </div>
複製程式碼

使用 jQuery,你可以移除這個 div 的 class 屬性:

$("#sample-div").removeAttr("class");
複製程式碼

當我們開始觀察,在呼叫 mutationObserver.observe(...) 之後我們將會在控制檯看到每個 MutationRecord 的日誌:

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

這個是由移除 class 屬性導致的變化。

最後,為了在任務結束後停止對 DOM 的觀察,你可以這樣做:

// 停止 MutationObserver 對變化的監聽。
mutationObserver.disconnect();
複製程式碼

現在,MutationObserver 已經被廣泛支援:

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

備擇方案

不管怎麼說,MutationObserver 並不是一直就有的。那麼當 MutationObserver 出現之前,開發者用的是什麼?

這是幾個可用的其他選項:

  • Polling
  • MutationEvents
  • CSS animations

Polling

最簡單的最接近原生的方法是 polling。使用瀏覽器的 setInterval web 介面你可以設定一個在一段時間後檢查是否有變化發生的的任務。自然,這個方法將會嚴重的降低應用或者網站的效能。

MutationEvents

在 2000 年,MutationEvents API 被引入。儘管很有用,但是每次 DOM 發生變化 mutation events 都會被觸發,這將再次導致效能問題。現在 MutationEvents 介面已經被廢棄,很快,現代瀏覽器將會完全停止對它的支援。

這是瀏覽器對 MutationEvents 的支援:

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

CSS animations

一個有點奇怪的備選方案依賴於 CSS animations。這可能聽起來有些讓人困惑。基本上,這個方案是建立一個動畫,一旦一個元素被新增到了 DOM,這個動畫就將會被觸發。動畫開始的時候,animationstart 事件會被觸發:如果你對這個事件繫結了一個處理器,你將會確切的知道元素是什麼時候被新增到 DOM 的。動畫執行的時間段很短,所以實際應用的時候它對使用者是不可見的。

首先,我們需要一個父級元素,我們在它的內部監聽節點的插入:

<div id=”container-element”></div>
複製程式碼

為了得到節點插入的處理器,我們需要設定一系列的 keyframe 動畫,當節點插入的時候,動畫將會開始。

@keyframes nodeInserted { 
 from { opacity: 0.99; }
 to { opacity: 1; } 
}
複製程式碼

keyframes 已經建立,動畫還需要被應用於你想要監聽的元素。注意應設定很小的 duration 值 —— 它們將會減弱動畫在瀏覽器上留下的痕跡:

#container-element * {
 animation-duration: 0.001s;
 animation-name: nodeInserted;
}
複製程式碼

這為 container-element 的所有子節點都新增了動畫。當動畫結束後,插入的事件將會被觸發。

我們需要一個作為事件監聽者的 JavaScript 方法。在方法內部,必須確保初始的 event.animationName 檢測是我們想要的那個動畫。

var insertionListener = function(event) {
  // 確保這是我們想要的那個動畫。
  if (event.animationName === "nodeInserted") {
    console.log("Node has been inserted: " + event.target);
  }
}
複製程式碼

現在是時候為父級元素新增事件監聽了:

document.addEventListener(“animationstart”, insertionListener, false); // standard + firefox
document.addEventListener(“MSAnimationStart”, insertionListener, false); // IE
document.addEventListener(“webkitAnimationStart”, insertionListener, false); // Chrome + Safari

複製程式碼

這是瀏覽器對於 CSS 動畫的支援:

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

MutationObserver 能提供上述提到的解決方案沒有的很多優點。本質上,它能覆蓋到每一個可能發生的 DOM 的變化,並且它會在一個批次的變化發生後被觸發,這種方法使得它得到大大的優化。最重要的,MutationObserver 被所有的主流現代瀏覽器所支援,還有一些使用引擎下 MutationEvents 的 polyfill。

MutationObserverSessionStack 庫中佔據了核心位置。

你一旦將 SessionStack 庫整合進 web 應用,它就開始收集 DOM 變化、網路請求、錯誤資訊、debug 資訊等等,併傳送到我們的伺服器。SessionStack 使用這些資訊重新建立了你的使用者端發生的一切,並以發生在使用者端的同樣的方式將產品的問題展現給你。很多使用者認為 SessionStack 記錄的實際是視訊 — 然而它並沒有。記錄真實情況的視訊是很耗費資源的,然而我們收集的少量資料卻很輕量,並不會影響 UX 和你的 web 應用的效能。

如果你想要嘗試一下 SessionStack,這是一個免費的設計案例。

[譯] JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化

掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章