JavaScript 工做原理之十-使用 MutationObserver 監測 DOM 變化

原文請查閱這裏,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Trolandjavascript

本系列持續更新中,Github 地址請查閱這裏java

這是 JavaScript 工做原理的第十章。node

網絡應用在客戶端日益複雜,這是由不少因素的形成的,好比須要更加豐富的界面交互以提供更加複雜的應用功能,實時計算等等。git

網絡應用的日益複雜致使沒法知曉其生命週期中指定時刻準確的交互界面狀態。github

若是你正在構建一些框架或者一個庫,這會更加的困難,好比,你沒法經過監測 DOM 來響應並執行一些特定的操做。web

概述

MutationObserver  是現代瀏覽器提供的用來檢測 DOM 變化的網頁接口。你可使用這個接口來監聽新增或者刪除節點,屬性更改,或者文本節點的內容更改。瀏覽器

能夠乾點啥好呢?bash

你能夠在如下幾種狀況信手拈來 MutationObserver 接口。好比:網絡

  • 通知用戶當前所在的頁面所發生的一些變化。
  • 經過使用一些很棒的 JavaScript 框架來根據 DOM 的變化來動態加載 JavaScript 模塊。
  • 可能當你在開發一個所見即所得編輯器的時候,使用 MutationObserver 接口來收集任意時間點上的更改,從而輕鬆地實現撤消/重作功能。

這只是幾個 MutationObserver 的使用場景。session

如何使用 MutationObserver

在應用中集成 MutationObserver 是至關簡單的。經過往構造函數 MutationObserver 中傳入一個函數做爲參數來初始化一個 MutationObserver 實例,該函數會在每次發生 DOM 發生變化的時候調用。MutationObserver 的函數的第一個參數即爲單個批處理中的 DOM 變化集。每一個變化包含了變化的類型和所發生的更改。

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

建立的實例對象擁有三個方法:

  • observe-開始進行監聽。接收兩個參數-要觀察的 DOM 節點以及一個配置對象。
  • disconnect-中止監聽變化。
  • takeRecords-觸發回調前返回最新的批量 DOM 變化。

如下爲開始監聽的代碼片斷:

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

如今,假設你寫了一個簡單的 div 元素:

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

可使用 jQuery 來移除 div 的 class 屬性:

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

當調用 mutationObserver.observe(…) 就能夠開始監聽 DOM 變化。

當每次發生 DOM 變化的時候,會打印出各個 MutationRecord 日誌信息:

這一變化是由移除 class 屬性所引發的。

最後,若是想中止監聽 DOM 變化可使用以下方法:

// MutationObserver 中止監聽 DOM 變化
mutationObserver.disconnect();
複製代碼

如今,MutationObserver 瀏覽器兼容狀況很好:

替代方法

然而,以前 MutationObserver 並無被普遍使用。那麼,當沒有 MutationObserver 的時候,開發者是如何解決監聽 DOM 變化的呢?

有幾下幾種可用的方法:

  • 輪詢
  • MutationEvents
  • CSS 動畫

輪詢

最簡單且粗糙的方法即便用輪詢。使用瀏覽器內置的 setInterval 網頁接口你能夠建立一個定時任務來定時檢查 DOM 的變化。固然了,這個方法會顯著地減弱網絡應用/網站的性能。

其實,這是能夠理解爲髒檢查,若是有使用過 AngularJS 應該會有看過其髒檢查所致使的性能問題。在個人另外一個系列裏面有稍微介紹了下,具體能夠查看這裏

MutationEvents

早在 2000 年,就推出了 MutationEvents API 。雖然挺管用的,可是每一個單一的 DOM 變化都會觸發 mutation 事件,結果又會形成性能問題。如今,MutationEvents 接口已經被廢棄,不久的未來,現代瀏覽器全都將中止支持該接口。

如下是 MutationEvents 的瀏覽器兼容狀況:

CSS 動畫

依靠 CSS 動畫 是一個有點使人感到新奇的替代方案。這聽起來會讓人有些困惑。大致上,實現思路是這樣的,建立一個動畫,一旦在 DOM 中添加一個元素就會觸發該動畫。開始執行 CSS 動畫的時候就會觸發 animationstart 事件:假設爲該事件添加事件監聽器,就能夠準確知曉 DOM 中添加元素的時機。動畫的運行時間週期必須很是的短以便讓用戶感知不到,即體驗更佳。

首先,須要一個父級元素,在裏面監聽節點添加事件:

<div id=」container-element」></div>
複製代碼

爲了處理節點的添加,須要建立關鍵幀序列動畫,該序動畫在添加節點的時候啓動:

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

建立好關鍵幀以後,在須要監聽的元素上應用動畫。注意到那個短暫的持續時間-在瀏覽器端動畫痕跡會很是平滑(即用戶會感受不到有動畫發生):

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

這樣會爲 container-element 的全部後代節點添加動畫。當動畫結束,觸發 insertion 事件。

咱們須要建立一個函數做爲事件監聽器。在函數內部,開始必須使用 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 動畫瀏覽器支持狀況:

相比以上幾種替代方案 MutationObserver 有幾點優點。本質上,它會監聽 DOM 可能發生的每一個變化而且性能更優,因其會批量 DOM 變化以後才觸發回調事件。總之,MutationObserver 的兼容性很好,而且還有一些墊片,這些墊片底層是基於 MutationEvents 的。

打個廣告 ^.^

今日頭條招人啦!發送簡歷到 likun.liyuk@bytedance.com ,便可走快速內推通道,長期有效!國際化PGC部門的JD以下:c.xiumi.us/board/v5/2H…,也可內推其餘部門!

本系列持續更新中,Github 地址請查閱這裏

相關文章
相關標籤/搜索