摘要: 掌握MutationObserver。
Fundebug經授權轉載,版權歸原作者所有。
這是專門探索 JavaScript 及其所構建的元件的系列文章的第10篇。
如果你錯過了前面的章節,可以在這裡找到它們:
- JavaScript 是如何工作的:引擎,執行時和呼叫堆疊的概述!
- JavaScript 是如何工作的:深入V8引擎&編寫優化程式碼的5個技巧!
- JavaScript 是如何工作的:記憶體管理+如何處理4個常見的記憶體洩漏 !
- JavaScript 是如何工作的:事件迴圈和非同步程式設計的崛起+ 5種使用 async/await 更好地編碼方式!
- JavaScript 是如何工作的:深入探索 websocket 和HTTP/2與SSE +如何選擇正確的路徑!
- JavaScript 是如何工作的:與 WebAssembly比較 及其使用場景 !
- JavaScript 是如何工作的:Web Workers的構建塊+ 5個使用他們的場景!
- JavaScript 是如何工作的:Service Worker 的生命週期及使用場景!
- JavaScript 是如何工作的:Web 推送通知的機制!
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,你可以知道在任何給定的點上進行了哪些更改,因此可以輕鬆地撤消這些更改。
這些只是 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 的日誌:
這個是由移除 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
已經被廣泛支援:
備擇方案
MutationObserver 在之前還沒有的,那麼在 MutationObserver 還沒出現之前,開發者採用什麼方案呢?
這是幾個可用的其他選項:
- 輪詢(Polling)
- MutationEvents
- CSS animations
輪詢(Polling)
最簡單和最簡單的方法是輪詢。使用瀏覽器 setInterval
方法,可以設定一個任務,定期檢查是否發生了任何更改。當然,這種方法會顯著降低web 應用程式/網站的效能。
MutationEvents
在2000年,MutationEvents API 被引入。雖然很有用,但在 DOM中 的每一次更改都會觸發改變事件,這同樣會導致效能問題。現在 MutationEvents API 已經被棄用,很快現代瀏覽器將完全停止支援它。
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動畫的支援情況:
MutationObserver 比上述解決方案有許多優點。本質上,它涵蓋了 DOM 中可能發生的每一個更改,並且在批量觸發更改時,它的優化程度更高。最重要的是,所有主要的現代瀏覽器都支援 MutationObserver,還有一些使用引擎下 MutationEvents 的 polyfill。
原文:
程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具Fundebug。
你的點贊是我持續分享好東西的動力,歡迎點贊!
一個笨笨的碼農,我的世界只能終身學習!
更多內容請關注公眾號《大遷世界》!