JavaScript Mutation Observer(變動觀察器)

2021-09-15 15:16 更新

概述

Mutation Observer(變動觀察器)是監(jiān)視DOM變動的接口。DOM發(fā)生任何變動,Mutation Observer會得到通知。

概念上,它很接近事件??梢岳斫鉃?,當DOM發(fā)生變動,會觸發(fā)Mutation Observer事件。但是,它與事件有一個本質(zhì)不同:事件是同步觸發(fā),也就是說,當DOM發(fā)生變動,立刻會觸發(fā)相應(yīng)的事件;Mutation Observer則是異步觸發(fā),DOM發(fā)生變動以后,并不會馬上觸發(fā),而是要等到當前所有DOM操作都結(jié)束后才觸發(fā)。

這樣設(shè)計是為了應(yīng)付DOM變動頻繁的特點。舉例來說,如果在文檔中連續(xù)插入1000個段落(p元素),就會連續(xù)觸發(fā)1000個插入事件,執(zhí)行每個事件的回調(diào)函數(shù),這很可能造成瀏覽器的卡頓;而Mutation Observer完全不同,只在1000個段落都插入結(jié)束后才會觸發(fā),而且只觸發(fā)一次。

Mutation Observer有以下特點:

  • 它等待所有腳本任務(wù)完成后,才會運行,即采用異步方式。

  • 它把DOM變動記錄封裝成一個數(shù)組進行處理,而不是一條條地個別處理DOM變動。

  • 它既可以觀察發(fā)生在DOM的所有類型變動,也可以觀察某一類變動。

目前,F(xiàn)irefox(14+)、 Chrome(26+)、Opera(15+)、IE(11+)和Safari(6.1+)支持這個API。Safari 6.0和Chrome 18-25使用這個API的時候,需要加上WebKit前綴(WebKitMutationObserver)??梢允褂孟旅娴谋磉_式,檢查當前瀏覽器是否支持這個API。

var MutationObserver = window.MutationObserver
  || window.WebKitMutationObserver
  || window.MozMutationObserver;

var observeMutationSupport = !!MutationObserver;

MutationObserver構(gòu)造函數(shù)

首先,使用MutationObserver構(gòu)造函數(shù),新建一個觀察器實例,同時指定這個實例的回調(diào)函數(shù)。

var observer = new MutationObserver(callback);

觀察器的回調(diào)函數(shù)會在每次DOM發(fā)生變動后調(diào)用。它接受兩個參數(shù),第一個是變動數(shù)組(詳見后文),第二個是觀察器實例。

Mutation Observer實例的方法

observe()

observe方法指定所要觀察的DOM節(jié)點,以及所要觀察的特定變動。

var article = document.querySelector('article');

var  options = {
  'childList': true,
  'attributes':true
} ;

observer.observe(article, options);

上面代碼中,observe方法接受兩個參數(shù),第一個是所要觀察的DOM元素是article,第二個是所要觀察的變動類型(子節(jié)點變動和屬性變動)。

觀察器所能觀察的DOM變動類型(即上面代碼的options對象),有以下幾種:

  • childList:子節(jié)點的變動。
  • attributes:屬性的變動。
  • characterData:節(jié)點內(nèi)容或節(jié)點文本的變動。
  • subtree:所有后代節(jié)點的變動。

想要觀察哪一種變動類型,就在option對象中指定它的值為true。需要注意的是,不能單獨觀察subtree變動,必須同時指定childList、attributes和characterData中的一種或多種。

除了變動類型,options對象還可以設(shè)定以下屬性:

  • attributeOldValue:類型為布爾值,表示觀察attributes變動時,是否需要記錄變動前的屬性值。

  • characterDataOldValue:類型為布爾值,表示觀察characterData變動時,是否需要記錄變動前的值。

  • attributeFilter:類型為數(shù)組,表示需要觀察的特定屬性(比如['class','src'])。

對一個節(jié)點添加觀察器,就像添加addEventListener方法一樣。多次添加同一個觀察器是無效的,回調(diào)函數(shù)依然只會觸發(fā)一次。但是,如果指定不同的options對象,就會被當作兩個不同的觀察器。

下面的例子觀察新增的子節(jié)點。

var insertedNodes = [];
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    for (var i = 0; i < mutation.addedNodes.length; i++)
      insertedNodes.push(mutation.addedNodes[i]);
  })
});
observer.observe(document, { childList: true });
console.log(insertedNodes);

disconnect(),takeRecords()

disconnect方法用來停止觀察。再發(fā)生相應(yīng)變動,就不再調(diào)用回調(diào)函數(shù)。

observer.disconnect();

takeRecords方法用來清除變動記錄,即不再處理未處理的變動。該方法返回變動記錄的數(shù)組。

observer.takeRecords();

MutationRecord對象

DOM每次發(fā)生變化,就會生成一條變動記錄。這個變動記錄對應(yīng)一個MutationRecord對象,該對象包含了與變動相關(guān)的所有信息。Mutation Observer處理的是一個個MutationRecord對象所組成的數(shù)組。

MutationRecord對象包含了DOM的相關(guān)信息,有如下屬性:

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

應(yīng)用示例

子元素的變動

下面的例子說明如何讀取變動記錄。

var callback = function(records){
  records.map(function(record){
    console.log('Mutation type: ' + record.type);
    console.log('Mutation target: ' + record.target);
  });
};

var mo = new MutationObserver(callback);

var option = {
  'childList': true,
  'subtree': true
};

mo.observe(document.body, option);

上面代碼的觀察器,觀察body的所有下級節(jié)點(childList表示觀察子節(jié)點,subtree表示觀察后代節(jié)點)的變動?;卣{(diào)函數(shù)會在控制臺顯示所有變動的類型和目標節(jié)點。

屬性的變動

下面的例子說明如何追蹤屬性的變動。

var callback = function(records){
  records.map(function(record){
    console.log('Previous attribute value: ' + record.oldValue);
  });
};

var mo = new MutationObserver(callback);

var element = document.getElementById('#my_element');

var options = {
  'attributes': true,
  'attributeOldValue': true
}

mo.observe(element, options);

上面代碼先設(shè)定追蹤屬性變動('attributes': true),然后設(shè)定記錄變動前的值。實際發(fā)生變動時,會將變動前的值顯示在控制臺。

取代DOMContentLoaded事件

網(wǎng)頁加載的時候,DOM節(jié)點的生成會產(chǎn)生變動記錄,因此只要觀察DOM的變動,就能在第一時間觸發(fā)相關(guān)事件,因此也就沒有必要使用DOMContentLoaded事件。

var observer = new MutationObserver(callback);
observer.observe(document.documentElement, {
  childList: true,
  subtree: true
});

上面代碼中,監(jiān)聽document.documentElement(即HTML節(jié)點)的子節(jié)點的變動,subtree屬性指定監(jiān)聽還包括后代節(jié)點。因此,任意一個網(wǎng)頁元素一旦生成,就能立刻被監(jiān)聽到。

下面的代碼,使用MutationObserver對象封裝一個監(jiān)聽DOM生成的函數(shù)。

(function(win){
  'use strict';

  var listeners = [];
  var doc = win.document;
  var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  var observer;

  function ready(selector, fn){
    // 儲存選擇器和回調(diào)函數(shù)
    listeners.push({
      selector: selector,
      fn: fn
    });
    if(!observer){
      // 監(jiān)聽document變化
      observer = new MutationObserver(check);
      observer.observe(doc.documentElement, {
        childList: true,
        subtree: true
      });
    }
    // 檢查該節(jié)點是否已經(jīng)在DOM中
    check();
  }

  function check(){
  // 檢查是否匹配已儲存的節(jié)點
    for(var i = 0; i < listeners.length; i++){
      var listener = listeners[i];
      // 檢查指定節(jié)點是否有匹配
      var elements = doc.querySelectorAll(listener.selector);
      for(var j = 0; j < elements.length; j++){
        var element = elements[j];
        // 確保回調(diào)函數(shù)只會對該元素調(diào)用一次
        if(!element.ready){
          element.ready = true;
          // 對該節(jié)點調(diào)用回調(diào)函數(shù)
          listener.fn.call(element, element);
        }
      }
    }
  }

  // 對外暴露ready
  win.ready = ready;

})(this);

ready('.foo', function(element){
  // ...
});

參考鏈接

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號