Mutation Observer API

概述

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

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

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

Mutation Observer 有以下特點。

  • 它等待所有腳本任務完成后,才會運行,即采用異步方式。
  • 它把 DOM 變動記錄封裝成一個數組進行處理,而不是一條條地個別處理 DOM 變動。
  • 它既可以觀察發生在 DOM 的所有類型變動,也可以觀察某一類變動。

MutationObserver 構造函數

使用時,首先使用MutationObserver構造函數,新建一個觀察器實例,同時指定這個實例的回調函數。

var observer = new MutationObserver(callback);

上面代碼中的回調函數,會在每次 DOM 變動后調用。該回調函數接受兩個參數,第一個是變動數組,第二個是觀察器實例,下面是一個例子。

var observer = new MutationObserver(function (mutations, observer) {
  mutations.forEach(function(mutation) {
    console.log(mutation);
  });
});

實例方法

observe()

observe方法用來開始監聽,它接受兩個參數。

  • 第一個參數是所要觀察的 DOM 節點
  • 第二個參數是一個配置對象,用來指定所要觀察的特定變動
var article = document.querySelector('article');

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

observer.observe(article, options);

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

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

  • childList:子節點的變動。
  • attributes:屬性的變動。
  • characterData:節點內容或節點文本的變動。
  • subtree:所有后代節點的變動。

想要觀察哪一種變動類型,就在option對象中指定它的值為true。需要注意的是,如果設置觀察subtree的變動,必須同時指定childListattributescharacterData中的一種或多種。

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

  • attributeOldValue:類型為布爾值,表示觀察attributes變動時,是否需要記錄變動前的屬性值。
  • characterDataOldValue:類型為布爾值,表示觀察characterData變動時,是否需要記錄變動前的值。
  • attributeFilter:類型為數組,表示需要觀察的特定屬性(比如['class','src'])。
// 開始監聽文檔根節點(即<html>標簽)的變動
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

對一個節點添加觀察器,就像使用addEventListener方法一樣,多次添加同一個觀察器是無效的,回調函數依然只會觸發一次。但是,如果指定不同的options對象,就會被當作兩個不同的觀察器。

下面的例子是觀察新增的子節點。

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方法用來停止觀察。調用該方法后,DOM 再發生變動,也不會觸發觀察器。

observer.disconnect();

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

observer.takeRecords();

下面是一個例子。

// 保存所有沒有被觀察器處理的變動
var changes = mutationObserver.takeRecords();

// 停止觀察
mutationObserver.disconnect();

MutationRecord 對象

DOM 每次發生變化,就會生成一條變動記錄。這個變動記錄對應一個MutationRecord對象,該對象包含了與變動相關的所有信息。Mutation Observer 處理的是一個個MutationRecord對象所組成的數組。

MutationRecord對象包含了DOM的相關信息,有如下屬性:

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

應用示例

子元素的變動

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

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>的所有下級節點(childList表示觀察子節點,subtree表示觀察后代節點)的變動。回調函數會在控制臺顯示所有變動的類型和目標節點。

屬性的變動

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

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);

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

取代 DOMContentLoaded 事件

網頁加載的時候,DOM 節點的生成會產生變動記錄,因此只要觀察 DOM 的變動,就能在第一時間觸發相關事件,因此也就沒有必要使用DOMContentLoaded事件。

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

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

下面的代碼,使用MutationObserver對象封裝一個監聽 DOM 生成的函數。

(function(win){
  'use strict';

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

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

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

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

})(this);

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

參考鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容

  • 1、概述 Mutation Observer(變動觀察器)是監視DOM變動的接口。當DOM對象樹發生任何變動時,M...
    hukei閱讀 2,647評論 0 0
  • MutationObserver翻譯過來就是變動觀察器,字面上就可以理解這是用來觀察Node(節點)變化的。Mut...
    螢火蟲de夢閱讀 1,298評論 1 0
  • 1 概述 Mutation observer 是用于代替 Mutation events 作為觀察DOM樹結構發生...
    falm閱讀 53,347評論 4 31
  • 80后的我早已不是第一次面對死亡了,年初舅舅走了。夢里依稀還是上學的時光,騎車路過舅舅家門口,陽光晴好大門卻鐵銹斑...
    小烏衣閱讀 212評論 0 0
  • 親愛的兔子 : 過了明天我們在一起就兩個月了,謝謝上帝讓這么聰慧漂亮的姑娘出現在我的生活,讓我單調的生活中多了一份...
    Curry_Chang閱讀 231評論 0 0