Mutation Observer API

概述

Mutation Observer API 用來(lái)監(jiān)視 DOM 變動(dòng)阐枣。DOM 的任何變動(dòng),比如節(jié)點(diǎn)的增減篇梭、屬性的變動(dòng)、文本內(nèi)容的變動(dòng)酝枢,這個(gè) API 都可以得到通知很洋。

概念上,它很接近事件隧枫,可以理解為當(dāng) DOM 發(fā)生變動(dòng)喉磁,就會(huì)觸發(fā) Mutation Observer 事件。但是官脓,它與事件有一個(gè)本質(zhì)不同:事件是同步觸發(fā)协怒,也就是說(shuō),DOM 的變動(dòng)立刻會(huì)觸發(fā)相應(yīng)的事件卑笨;Mutation Observer 則是異步觸發(fā)孕暇,DOM 的變動(dòng)并不會(huì)馬上觸發(fā),而是要等到當(dāng)前所有 DOM 操作都結(jié)束才觸發(fā)。

這樣設(shè)計(jì)是為了應(yīng)付 DOM 變動(dòng)頻繁的特點(diǎn)妖滔。舉例來(lái)說(shuō)隧哮,如果在文檔中連續(xù)插入1000個(gè)<p>元素,就會(huì)連續(xù)觸發(fā)1000個(gè)插入事件座舍,執(zhí)行每個(gè)事件的回調(diào)函數(shù)沮翔,這很可能造成瀏覽器的卡頓;而 Mutation Observer 完全不同曲秉,只在1000個(gè)段落都插入結(jié)束后才會(huì)觸發(fā)采蚀,而且只觸發(fā)一次。

Mutation Observer 有以下特點(diǎn)承二。

  • 它等待所有腳本任務(wù)完成后榆鼠,才會(huì)運(yùn)行,即采用異步方式亥鸠。
  • 它把 DOM 變動(dòng)記錄封裝成一個(gè)數(shù)組進(jìn)行處理妆够,而不是一條條地個(gè)別處理 DOM 變動(dòng)。
  • 它既可以觀察發(fā)生在 DOM 的所有類(lèi)型變動(dòng)负蚊,也可以觀察某一類(lèi)變動(dòng)责静。

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

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

var observer = new MutationObserver(callback);

上面代碼中的回調(diào)函數(shù)揩徊,會(huì)在每次 DOM 變動(dòng)后調(diào)用腰鬼。該回調(diào)函數(shù)接受兩個(gè)參數(shù),第一個(gè)是變動(dòng)數(shù)組塑荒,第二個(gè)是觀察器實(shí)例熄赡,下面是一個(gè)例子。

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

實(shí)例方法

observe()

observe方法用來(lái)開(kāi)始監(jiān)聽(tīng)齿税,它接受兩個(gè)參數(shù)彼硫。

  • 第一個(gè)參數(shù)是所要觀察的 DOM 節(jié)點(diǎn)
  • 第二個(gè)參數(shù)是一個(gè)配置對(duì)象,用來(lái)指定所要觀察的特定變動(dòng)
var article = document.querySelector('article');

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

observer.observe(article, options);

上面代碼中凌箕,observe方法接受兩個(gè)參數(shù)拧篮,第一個(gè)是所要觀察的DOM元素是article,第二個(gè)是所要觀察的變動(dòng)類(lèi)型(子節(jié)點(diǎn)變動(dòng)和屬性變動(dòng))牵舱。

觀察器所能觀察的 DOM 變動(dòng)類(lèi)型(即上面代碼的options對(duì)象)串绩,有以下幾種。

  • childList:子節(jié)點(diǎn)的變動(dòng)芜壁。
  • attributes:屬性的變動(dòng)礁凡。
  • characterData:節(jié)點(diǎn)內(nèi)容或節(jié)點(diǎn)文本的變動(dòng)高氮。
  • subtree:所有后代節(jié)點(diǎn)的變動(dòng)。

想要觀察哪一種變動(dòng)類(lèi)型顷牌,就在option對(duì)象中指定它的值為true剪芍。需要注意的是,如果設(shè)置觀察subtree的變動(dòng)窟蓝,必須同時(shí)指定childList罪裹、attributescharacterData中的一種或多種。

除了變動(dòng)類(lèi)型疗锐,options對(duì)象還可以設(shè)定以下屬性:

  • attributeOldValue:類(lèi)型為布爾值,表示觀察attributes變動(dòng)時(shí)费彼,是否需要記錄變動(dòng)前的屬性值滑臊。
  • characterDataOldValue:類(lèi)型為布爾值,表示觀察characterData變動(dòng)時(shí)箍铲,是否需要記錄變動(dòng)前的值雇卷。
  • attributeFilter:類(lèi)型為數(shù)組,表示需要觀察的特定屬性(比如['class','src'])颠猴。
// 開(kāi)始監(jiān)聽(tīng)文檔根節(jié)點(diǎn)(即<html>標(biāo)簽)的變動(dòng)
mutationObserver.observe(document.documentElement, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
});

對(duì)一個(gè)節(jié)點(diǎn)添加觀察器关划,就像使用addEventListener方法一樣,多次添加同一個(gè)觀察器是無(wú)效的翘瓮,回調(diào)函數(shù)依然只會(huì)觸發(fā)一次贮折。但是,如果指定不同的options對(duì)象资盅,就會(huì)被當(dāng)作兩個(gè)不同的觀察器调榄。

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

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方法用來(lái)停止觀察每庆。調(diào)用該方法后,DOM 再發(fā)生變動(dòng)今穿,也不會(huì)觸發(fā)觀察器缤灵。

observer.disconnect();

takeRecords方法用來(lái)清除變動(dòng)記錄,即不再處理未處理的變動(dòng)蓝晒。該方法返回變動(dòng)記錄的數(shù)組腮出。

observer.takeRecords();

下面是一個(gè)例子。

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

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

MutationRecord 對(duì)象

DOM 每次發(fā)生變化芝薇,就會(huì)生成一條變動(dòng)記錄利诺。這個(gè)變動(dòng)記錄對(duì)應(yīng)一個(gè)MutationRecord對(duì)象,該對(duì)象包含了與變動(dòng)相關(guān)的所有信息剩燥。Mutation Observer 處理的是一個(gè)個(gè)MutationRecord對(duì)象所組成的數(shù)組慢逾。

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

  • type:觀察的變動(dòng)類(lèi)型(attributecharacterData或者childList)侣滩。
  • target:發(fā)生變動(dòng)的DOM節(jié)點(diǎn)口注。
  • addedNodes:新增的DOM節(jié)點(diǎn)。
  • removedNodes:刪除的DOM節(jié)點(diǎn)君珠。
  • previousSibling:前一個(gè)同級(jí)節(jié)點(diǎn)寝志,如果沒(méi)有則返回null
  • nextSibling:下一個(gè)同級(jí)節(jié)點(diǎn)策添,如果沒(méi)有則返回null材部。
  • attributeName:發(fā)生變動(dòng)的屬性。如果設(shè)置了attributeFilter唯竹,則只返回預(yù)先指定的屬性乐导。
  • oldValue:變動(dòng)前的值。這個(gè)屬性只對(duì)attributecharacterData變動(dòng)有效浸颓,如果發(fā)生childList變動(dòng)物臂,則返回null

應(yīng)用示例

子元素的變動(dòng)

下面的例子說(shuō)明如何讀取變動(dò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>的所有下級(jí)節(jié)點(diǎn)(childList表示觀察子節(jié)點(diǎn),subtree表示觀察后代節(jié)點(diǎn))的變動(dòng)晋涣∫敲剑回調(diào)函數(shù)會(huì)在控制臺(tái)顯示所有變動(dòng)的類(lèi)型和目標(biāo)節(jié)點(diǎn)。

屬性的變動(dòng)

下面的例子說(shuō)明如何追蹤屬性的變動(dòng)谢鹊。

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è)定追蹤屬性變動(dòng)('attributes': true)规丽,然后設(shè)定記錄變動(dòng)前的值。實(shí)際發(fā)生變動(dòng)時(shí)撇贺,會(huì)將變動(dòng)前的值顯示在控制臺(tái)赌莺。

取代 DOMContentLoaded 事件

網(wǎng)頁(yè)加載的時(shí)候,DOM 節(jié)點(diǎn)的生成會(huì)產(chǎn)生變動(dòng)記錄松嘶,因此只要觀察 DOM 的變動(dòng)艘狭,就能在第一時(shí)間觸發(fā)相關(guān)事件,因此也就沒(méi)有必要使用DOMContentLoaded事件翠订。

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

上面代碼中巢音,監(jiān)聽(tīng)document.documentElement(即HTML節(jié)點(diǎn))的子節(jié)點(diǎn)的變動(dòng),subtree屬性指定監(jiān)聽(tīng)還包括后代節(jié)點(diǎn)尽超。因此官撼,任意一個(gè)網(wǎng)頁(yè)元素一旦生成,就能立刻被監(jiān)聽(tīng)到似谁。

下面的代碼傲绣,使用MutationObserver對(duì)象封裝一個(gè)監(jiān)聽(tīng) DOM 生成的函數(shù)掠哥。

(function(win){
  'use strict';

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

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

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

  // 對(duì)外暴露ready
  win.ready = ready;

})(this);

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

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秃诵,一起剝皮案震驚了整個(gè)濱河市续搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菠净,老刑警劉巖禁舷,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異毅往,居然都是意外死亡牵咙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)攀唯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洁桌,“玉大人,你說(shuō)我怎么就攤上這事革答≌嚼ぃ” “怎么了曙强?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵残拐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碟嘴,道長(zhǎng)溪食,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任娜扇,我火速辦了婚禮错沃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雀瓢。我一直安慰自己枢析,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布刃麸。 她就那樣靜靜地躺著醒叁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泊业。 梳的紋絲不亂的頭發(fā)上把沼,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音吁伺,去河邊找鬼饮睬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篮奄,可吹牛的內(nèi)容都是我干的捆愁。 我是一名探鬼主播割去,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牙瓢!你這毒婦竟也來(lái)了劫拗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤矾克,失蹤者是張志新(化名)和其女友劉穎页慷,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體胁附,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酒繁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了控妻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片州袒。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弓候,靈堂內(nèi)的尸體忽然破棺而出郎哭,到底是詐尸還是另有隱情,我是刑警寧澤菇存,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布夸研,位于F島的核電站,受9級(jí)特大地震影響依鸥,放射性物質(zhì)發(fā)生泄漏亥至。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一贱迟、第九天 我趴在偏房一處隱蔽的房頂上張望姐扮。 院中可真熱鬧,春花似錦衣吠、人聲如沸茶敏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惊搏。三九已至,卻和暖如春袍榆,著一層夾襖步出監(jiān)牢的瞬間胀屿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工包雀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宿崭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓才写,卻偏偏與公主長(zhǎng)得像葡兑,于是被迫代替她去往敵國(guó)和親奖蔓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1讹堤、概述 Mutation Observer(變動(dòng)觀察器)是監(jiān)視DOM變動(dòng)的接口吆鹤。當(dāng)DOM對(duì)象樹(shù)發(fā)生任何變動(dòng)時(shí),M...
    hukei閱讀 2,623評(píng)論 0 0
  • MutationObserver翻譯過(guò)來(lái)就是變動(dòng)觀察器洲守,字面上就可以理解這是用來(lái)觀察Node(節(jié)點(diǎn))變化的疑务。Mut...
    螢火蟲(chóng)de夢(mèng)閱讀 1,277評(píng)論 1 0
  • 1 概述 Mutation observer 是用于代替 Mutation events 作為觀察DOM樹(shù)結(jié)構(gòu)發(fā)生...
    falm閱讀 53,199評(píng)論 4 31
  • 80后的我早已不是第一次面對(duì)死亡了,年初舅舅走了梗醇。夢(mèng)里依稀還是上學(xué)的時(shí)光知允,騎車(chē)路過(guò)舅舅家門(mén)口,陽(yáng)光晴好大門(mén)卻鐵銹斑...
    小烏衣閱讀 201評(píng)論 0 0
  • 親愛(ài)的兔子 : 過(guò)了明天我們?cè)谝黄鹁蛢蓚€(gè)月了叙谨,謝謝上帝讓這么聰慧漂亮的姑娘出現(xiàn)在我的生活温鸽,讓我單調(diào)的生活中多了一份...
    Curry_Chang閱讀 225評(píng)論 0 0