JavaScript是如何工作的:使用MutationObserver跟蹤DOM的變化

Fundebug發表於2019-01-10

摘要: 掌握MutationObserver。

Fundebug經授權轉載,版權歸原作者所有。

這是專門探索 JavaScript 及其所構建的元件的系列文章的第10篇。

如果你錯過了前面的章節,可以在這裡找到它們:

Web 應用程式在客戶端變得越來越重,原因很多,例如需要更豐富的 UI 來容納更復雜的應用程式提供的內容,實時計算等等。複雜性的增加使得在 Web 應用程式生命週期的每個給定時刻都很難知道 UI 的確切狀態。

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

概述

Mutation Observer API 用來監視 DOM 變動。DOM 的任何變動,比如節點的增減、屬性的變動、文字內容的變動,這個 API 都可以得到通知。

概念上,它很接近事件,可以理解為 DOM 發生變動就會觸發 Mutation Observer 事件。但是,它與事件有一個本質不同:事件是同步觸發,也就是說,DOM 的變動立刻會觸發相應的事件;Mutation Observer 則是非同步觸發,DOM 的變動並不會馬上觸發,而是要等到當前所有 DOM 操作都結束才觸發。

這樣設計是為了應付 DOM 變動頻繁的特點。舉例來說,如果文件中連續插入1000個 <li>元素,就會連續觸發1000個插入事件,執行每個事件的回撥函式,這很可能造成瀏覽器的卡頓;而 Mutation Observer 完全不同,只在 1000 個段落都插入結束後才會觸發,而且只觸發一次。

Mutation Observer有以下特點:

  • 它等待所有指令碼任務完成後,才會執行,即採用非同步方式
  • 它把 DOM 變動記錄封裝成一個陣列進行處理,而不是一條條地個別處理 DOM 變動
  • 它即可以觀察發生在 DOM 節點的所有變動,也可以觀察某一類變動

為什麼要要監聽 DOM?

在很多情況下,MutationObserver API 都可以派上用場。例如:

  • 你希望通知 Web 應用程式訪問者,他當前所在的頁面發生了一些更改。
  • 你正在開發一個新的 JavaScript 框架,需要根據 DOM 的變化動態載入 JavaScript 模組。
  • 也許你正在開發一個所見即所得(WYSIWYG) 編輯器,試圖實現撤消/重做功能。通過利用 MutationObserver API,你可以知道在任何給定的點上進行了哪些更改,因此可以輕鬆地撤消這些更改。

JavaScript是如何工作的:使用MutationObserver跟蹤DOM的變化

這些只是 MutationObserver 可以提供幫助的幾個例子。

MutationObserver 用法

在應用程式中實現 MutationObserver 相當簡單。你需要通過傳入一個函式來建立一個 MutationObserver 例項,每當有變化發生,這個函式將會被呼叫。函式的第一個引數是變動陣列,每個變化都會提供它的型別和已經發生的變化的資訊。

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

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

  • observe  — 啟動監聽
  • disconnect — 用來停止觀察
  • takeRecords — 返用來清除變動記錄,即不再處理未處理的變動。

observe()

observe 方法用來啟動監聽,它接受兩個引數。

  • 第一個引數:所要觀察的 DOM 節點
  • 第二個引數:一個配置物件,指定所要觀察的特定變

下面的片段展示瞭如何開始啟動監聽(observe  ):

// 開始偵聽頁面的根 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 屬性導致的變化。

MutationRecord 物件包含了DOM的相關資訊,有如下屬性:

**type:**觀察的變動型別(attribute、characterData或者childList) **target:**發生變動的 DOM 節點 **addedNodes:**新增的 DOM 節點 **removedNodes:**刪除的 DOM 節點 **previousSibling:**前一個同級節點,如果沒有則返回 null **nextSibling:**下一個同級節點,如果沒有則返回 null **attributeName:**發生變動的屬性。如果設定了attributeFilter,則只返回預先指定的屬性。 **oldValue:**變動前的值。這個屬性只對 attribute 和 characterData 變動有效,如果發生 childList 變動,則返回 null

最後,為了在任務完成後停止觀察 DOM,可以執行以下操作:

mutationObserver.disconnect();
複製程式碼

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

JavaScript是如何工作的:使用MutationObserver跟蹤DOM的變化

備擇方案

MutationObserver 在之前還沒有的,那麼在 MutationObserver 還沒出現之前,開發者採用什麼方案呢?

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

  • 輪詢(Polling)
  • MutationEvents
  • CSS animations

輪詢(Polling)

最簡單和最簡單的方法是輪詢。使用瀏覽器 setInterval 方法,可以設定一個任務,定期檢查是否發生了任何更改。當然,這種方法會顯著降低web 應用程式/網站的效能。

MutationEvents

在2000年,MutationEvents API 被引入。雖然很有用,但在 DOM中 的每一次更改都會觸發改變事件,這同樣會導致效能問題。現在 MutationEvents API 已經被棄用,很快現代瀏覽器將完全停止支援它。

JavaScript是如何工作的:使用MutationObserver跟蹤DOM的變化

CSS animations

另一個有點奇怪的選擇是依賴 CSS 動畫。這聽起來可能有點令人困惑。基本上,我們的想法是建立一個動畫,一旦元素被新增到 DOM 中,動畫就會被觸發。動畫開始的那一刻,animationstart 事件將被觸發:如果已經將事件處理程式附加到該事件,那麼你將確切地知道元素何時被新增到 DOM 中。動畫的執行時間週期應該很小,使用者幾乎看不到它。

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

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

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

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

建立 keyfram 後,還需要把它放入你想監聽的元素上,注意應設定很小的 duration 值 —— 它們將會減弱動畫在瀏覽器上留下的痕跡。

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

這會將動畫新增到 container-element 的所有子節點。 動畫結束時,將觸發插入事件。

我們需要一個 JavaScript 函式作為事件監聽器。在函式中,必須進行初始的 event.animationName 檢查以確保它是我們想要的動畫。

var insertionListener = function(event) {
  // Making sure that this is the animation we want.
  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。

原文:

blog.sessionstack.com...

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug

你的點贊是我持續分享好東西的動力,歡迎點贊!

一個笨笨的碼農,我的世界只能終身學習!

更多內容請關注公眾號《大遷世界》!

相關文章