MutationObserver
是用於代替 MutationEvents
做爲觀察 DOM
樹結構發生變化時,作出相應處理的 API
。爲何要使用 MutationObserver
去代替 MutationEvents
呢,咱們先了解一下 MutationEvents
html
它簡單的用法以下:node
document.getElementById('list').addEventListener( 'DOMSubtreeModified', () => { console.log('列表中子元素被修改') }, false )
// Mutation 事件列表 DOMAttrModified // 監聽元素的修改 DOMAttributeNameChanged DOMCharacterDataModified DOMElementNameChanged DOMNodeInserted // 監聽新增 DOMNodeRemoved // 監聽刪除 DOMNodeInsertedIntoDocument DOMSubtreeModified // 監聽子元素的修改
其中 DOMNodeRemoved
,DOMNodeInserted
和 DOMSubtreeModified
分別用於監聽元素子項的刪除,新增,修改(包括刪除和新增),DOMAttrModified
是監聽元素屬性的修改,而且可以提供具體的修改動做。數組
Mutation Events 遇到的問題瀏覽器
MutationEvents
。Webkit 內核不支持 DOMAttrModified
特性,DOMElementNameChanged
和 DOMAttributeNameChanged
在 Firefox 上不被支持。MutationEvents
是同步執行的,它的每次調用,都須要從事件隊列中取出事件,執行,而後事件隊列中移除,期間須要移動隊列元素。若是事件觸發的較爲頻繁的話,每一次都須要執行上面的這些步驟,那麼瀏覽器會被拖慢。 2. MutationEvents
自己是事件,因此捕獲是採用的是事件冒泡的形式,若是冒泡捕獲期間又觸發了其餘的 MutationEvents
的話,頗有可能就會致使阻塞 Javascript 線程,甚至致使瀏覽器崩潰。MutationObserver
是在 DOM4
中定義的,用於替代 MutationEvents
的新 API,它的不一樣於 events 的是,全部監聽操做以及相應處理都是在其餘腳本執行完成以後異步執行的,而且是因此變更觸發以後,將變得記錄在數組中,統一進行回調的,也就是說,當你使用 observer
監聽多個 DOM 變化時,而且這若干個 DOM 發生了變化,那麼 observer
會將變化記錄到變化數組中,等待一塊兒都結束了,而後一次性的從變化數組中執行其對應的回調函數。app
特色dom
目前,Firefox(14+)、Chrome(26+)、Opera(15+)、IE(11+) 和 Safari(6.1+) 支持這個 API。 Safari 6.0 和 Chrome 18-25 使用這個 API 的時候,須要加上 WebKit 前綴(WebKitMutationObserver)。可使用下面的表達式檢查瀏覽器是否支持這個 API。異步
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver // 監測瀏覽器是否支持 const observeMutationSupport = !!MutationObserver
在應用中集成 MutationObserver
是至關簡單的。經過往構造函數 MutationObserver
中傳入一個函數做爲參數來初始化一個 MutationObserver
實例,該函數會在每次發生 DOM 發生變化的時候調用。MutationObserver
的函數的第一個參數即爲單個批處理中的 DOM 變化集。每一個變化包含了變化的類型和所發生的更改。編輯器
const mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { console.log(mutation) }) })
建立的實例對象擁有三個方法:函數
observe
-開始進行監聽。接收兩個參數-要觀察的 DOM 節點以及一個配置對象。disconnect
-中止監聽變化。takeRecords
-觸發回調前返回最新的批量 DOM 變化。observer 方法指定所要觀察的 DOM 元素,以及要觀察的特定變更。性能
const article = document.querySelector('article') observer.observer(article, { childList: true, arrtibutes: true })
上面代碼分析:
observer
對象 observer
方法。const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver // 選擇目標節點 const target = document.querySelector('#some-id') // 建立觀察者對象 const observer = new MutationObserver(mutation => { mutations.forEach(function(mutation) { console.log(mutation.type) }) }) // 配置觀察選項: const config = { attributes: true, childList: true, characterData: true } // 傳入目標節點和觀察選項 observer.observe(target, config) // 隨後,你還能夠中止觀察 observer.disconnect()
takeRecord 方法用來清除變更記錄,即再也不處理未處理的變更。
在觀察者對象上調用 takeRecords
會返回 其觀察節點上的變化記錄(MutationRecord)數組。其中 MutationRecord
數組也會做爲,觀察者初始化時的回調函數的第一個參數。
其包含的屬性以下:
attributes
.若是是一個CharacterData
節點發生變化,則返回 characterData
,若是是目標節點的某個子節點發生了變化,則返回 childList
.attributes
,則返回發生變化的屬性節點所在的元素節點,若是 type 值爲 characterData
,則返回發生變化的這個 characterData 節點.若是 type 爲 childList
,則返回發生變化的子節點的父節點.observer.takeRecord()
MutationObserver
所觀察的 DOM 變更(即上面代碼的 option 對象),包含如下類型:
想要觀察哪種變更類型,就在 option 對象中指定它的值爲 true。
須要注意的是,不能單獨觀察 subtree 變更,必須同時指定 childList、attributes 和 characterData 中的一種或多種。
除了變更類型,option 對象還能夠設定如下屬性:
建立 MutationObserver
並 獲取 dom 元素,定義回調數據。
// 獲取MutationObserver,兼容低版本的瀏覽器 const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver // 獲取dom元素 const list = document.querySelector('ol') // 建立Observer const Observer = new MutationObserver((mutations, instance) => { console.log(mutations) console.log(instance) mutations.forEach(mutation => { console.log(mutation) }) })
Observer.observe(list, { childList: true, subtree: true }) // 追加div標籤 list.appendChild(document.createElement('div')) // 追加文本 list.appendChild(document.createTextNode('foo')) // 移除第一個節點 list.removeChild(list.childNodes[0]) // 子節點移除建立的div list.childNodes[0].appendChild(document.createElement('div'))
Observer.observe(list, { childList: true, characterData: true, subtree: true }) // 將第一個子節點的數據改成cha list.childNodes[0].data = 'cha'
Observer.observe(list, { attributes: true }) // 設置節點的屬性 會觸發回調函數 list.setAttribute('data-value', '111') // 從新設置屬性 會觸發回調 list.setAttribute('data-value', '2222') // 刪除屬性 也會觸發回調 list.removeAttribute('data-value')
Observer.observe(list, { attributes: true, attributeOldValue: true }) // 設置節點的屬性 會觸發回調函數 list.setAttribute('data-value', '111') // 刪除屬性 list.setAttribute('data-value', '2222')
Observer.observe(list, { childList: true, characterData: true, subtree: true, characterDataOldValue: true }) // 設置數據 觸發回調 list.childNodes[0].data = 'aaa' // 從新設置數據 從新觸發回調 list.childNodes[0].data = 'bbbb'
Observer.observe(list, { attributes: true, attributeFilter: ['data-value'] }) // 第一次設置屬性 data-key 不會觸發的,由於data-value 不存在 list.setAttribute('data-key', 1) // 第二次會觸發 list.setAttribute('data-value', 1)
下面咱們作一個簡單的 demo 編輯器:
ol
設置 contenteditable
讓容器可編輯;observer
監聽子元素的變化;<div id="demo"> <ol contenteditable style="border: 1px solid red"> <li>111111</li> </ol> </div>
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver const list = document.querySelector('ol') const Observer = new MutationObserver((mutations, instance) => { mutations.forEach(mutation => { if (mutation.type === 'childList') { const list_values = [].slice .call(list.children) .map(node => node.innerHTML) .filter(s => s !== '<br>') console.log(list_values) } }) }) Observer.observe(list, { childList: true })
如今咱們繼續能夠作一個相似於 input 和 textarea 中的 valueChange
的事件同樣的,監聽值變化,以前的值和以後的值,以下代碼:
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver const list = document.querySelector('ol') const Observer = new MutationObserver((mutations, instance) => { mutations.forEach(mutation => { const enter = { mutation: mutation, el: mutation.target, newValue: mutation.target.textContent, oldValue: mutation.oldValue } console.log(enter) }) }) Observer.observe(list, { childList: true, attributes: true, characterData: true, subtree: true, characterDataOldValue: true })
注意: 對 input 和 textarea 不起做用的。
<div id="editor" contenteditable style="width: 240px; height: 80px; border: 1px solid red;" ></div> <p id="textInput">還能夠輸入100字</p>
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver const editor = document.querySelector('#editor') const textInput = document.querySelector('#textInput') const observer = new MutationObserver(mutations => { mutations.forEach(function(mutation) { if (mutation.type === 'characterData') { const newValue = mutation.target.textContent textInput.innerHTML = `還能夠輸入${1000 - newValue.length}字` } }) }) observer.observe(editor, { childList: true, attributes: true, characterData: true, subtree: true, characterDataOldValue: true })