?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
概述
MutationObserver構(gòu)造函數(shù)
Mutation Observer實(shí)例的方法
observe()
disconnect(),takeRecords()
MutationRecord對(duì)象
應(yīng)用示例
子元素的變動(dòng)
屬性的變動(dòng)
取代DOMContentLoaded事件
參考鏈接
Mutation Observer(變動(dòng)觀察器)是監(jiān)視DOM變動(dòng)的接口。DOM發(fā)生任何變動(dòng),Mutation Observer會(huì)得到通知。
概念上,它很接近事件??梢岳斫鉃?,當(dāng)DOM發(fā)生變動(dòng),會(huì)觸發(fā)Mutation Observer事件。但是,它與事件有一個(gè)本質(zhì)不同:事件是同步觸發(fā),也就是說,當(dāng)DOM發(fā)生變動(dòng),立刻會(huì)觸發(fā)相應(yīng)的事件;Mutation Observer則是異步觸發(fā),DOM發(fā)生變動(dòng)以后,并不會(huì)馬上觸發(fā),而是要等到當(dāng)前所有DOM操作都結(jié)束后才觸發(fā)。
這樣設(shè)計(jì)是為了應(yīng)付DOM變動(dòng)頻繁的特點(diǎn)。舉例來說,如果在文檔中連續(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的所有類型變動(dòng),也可以觀察某一類變動(dòng)。
目前,F(xiàn)irefox(14+)、 Chrome(26+)、Opera(15+)、IE(11+)和Safari(6.1+)支持這個(gè)API。Safari 6.0和Chrome 18-25使用這個(gè)API的時(shí)候,需要加上WebKit前綴(WebKitMutationObserver)??梢允褂孟旅娴谋磉_(dá)式,檢查當(dāng)前瀏覽器是否支持這個(gè)API。
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var observeMutationSupport = !!MutationObserver;
首先,使用MutationObserver構(gòu)造函數(shù),新建一個(gè)觀察器實(shí)例,同時(shí)指定這個(gè)實(shí)例的回調(diào)函數(shù)。
var observer = new MutationObserver(callback);
觀察器的回調(diào)函數(shù)會(huì)在每次DOM發(fā)生變動(dòng)后調(diào)用。它接受兩個(gè)參數(shù),第一個(gè)是變動(dòng)數(shù)組(詳見后文),第二個(gè)是觀察器實(shí)例。
observe方法指定所要觀察的DOM節(jié)點(diǎn),以及所要觀察的特定變動(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)類型(子節(jié)點(diǎn)變動(dòng)和屬性變動(dòng))。
觀察器所能觀察的DOM變動(dòng)類型(即上面代碼的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)類型,就在option對(duì)象中指定它的值為true。需要注意的是,不能單獨(dú)觀察subtree變動(dòng),必須同時(shí)指定childList、attributes和characterData中的一種或多種。
除了變動(dòng)類型,options對(duì)象還可以設(shè)定以下屬性:
attributeOldValue:類型為布爾值,表示觀察attributes變動(dòng)時(shí),是否需要記錄變動(dòng)前的屬性值。
characterDataOldValue:類型為布爾值,表示觀察characterData變動(dòng)時(shí),是否需要記錄變動(dòng)前的值。
attributeFilter:類型為數(shù)組,表示需要觀察的特定屬性(比如['class','src'])。
對(duì)一個(gè)節(jié)點(diǎn)添加觀察器,就像添加addEventListener方法一樣。多次添加同一個(gè)觀察器是無效的,回調(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方法用來停止觀察。再發(fā)生相應(yīng)變動(dòng),就不再調(diào)用回調(diào)函數(shù)。
observer.disconnect();
takeRecords方法用來清除變動(dòng)記錄,即不再處理未處理的變動(dòng)。該方法返回變動(dòng)記錄的數(shù)組。
observer.takeRecords();
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)類型(attribute、characterData或者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),如果沒有則返回null。
nextSibling:下一個(gè)同級(jí)節(jié)點(diǎn),如果沒有則返回null。
attributeName:發(fā)生變動(dòng)的屬性。如果設(shè)置了attributeFilter,則只返回預(yù)先指定的屬性。
oldValue:變動(dòng)前的值。這個(gè)屬性只對(duì)attribute和characterData變動(dòng)有效,如果發(fā)生childList變動(dòng),則返回null。
下面的例子說明如何讀取變動(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)的類型和目標(biāo)節(jié)點(diǎn)。
下面的例子說明如何追蹤屬性的變動(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)。
網(wǎng)頁加載的時(shí)候,DOM節(jié)點(diǎn)的生成會(huì)產(chǎn)生變動(dòng)記錄,因此只要觀察DOM的變動(dòng),就能在第一時(shí)間觸發(fā)相關(guān)事件,因此也就沒有必要使用DOMContentLoaded事件。
var observer = new MutationObserver(callback); observer.observe(document.documentElement, { childList: true, subtree: true });
上面代碼中,監(jiān)聽document.documentElement(即HTML節(jié)點(diǎn))的子節(jié)點(diǎn)的變動(dòng),subtree屬性指定監(jiān)聽還包括后代節(jié)點(diǎn)。因此,任意一個(gè)網(wǎng)頁元素一旦生成,就能立刻被監(jiān)聽到。
下面的代碼,使用MutationObserver對(duì)象封裝一個(gè)監(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){ // 儲(chǔ)存選擇器和回調(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é)點(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){ // ... });
Tiffany Brown, Getting to know mutation observers
Michal Budzynski, JavaScript: The less known parts. DOM Mutations