閱讀目錄
1.理解 Mutation Observer
Mutation Observer(變動觀察器) 是監聽DOM變動的接口,DOM發生任何變動,Mutation Observer會得到通知。
它與事件類似,但有所不同,事件是同步的,也就是說DOM發生變動,事件立刻會處理,而Mutation Observer則是異步,它不會立即處理,而是等頁面上所有的DOM完成后,執行一次,如果頁面上要操作100次DOM的話,那么事件會監聽100次DOM,而Mutation Observer只會執行一次,等所有的DOM操作完成后,執行。
它的特點是:
1. 等待所有腳本任務完成后,才會執行,即采用異步方式。
2. DOM的變動記錄封裝成一個數組進行處理,而不是一條條的個別處理DOM變動。
3. 還可以觀測發生在DOM的所有類型變動,也可以觀測某一類變動。
瀏覽器支持如下:
下面的代碼是檢測瀏覽器是否支持該屬性。如下代碼:
var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver; // 監測瀏覽器是否支持 var observeMutationSupport = !!MutationObserver;
MutationObserver 構造函數
使用 MutationObserver 構造函數,新建一個觀察器實例,同時指定該實例的回調函數。如下:
var observer = new MutationObserver(callback);
觀察器回調函數會在每次DOM發生變動后調用,接受2個參數,第一個是變動數組,第二個是觀察器實例。
Mutation Observer 實例的方法
1. observe() 該方法所要觀察的DOM節點,以及所要觀察的特定變動。
該方法接受2個參數,第一個參數是所要觀察的DOM元素,第二個所要觀察的變動類型。
調用方式:observer.observe(dom, options);
那么類型有如下:
childList: 子節點的變動。
attributes: 屬性的變動。
characterData: 節點內容或節點文本的變動。
subtree 所有后代節點的變動。
需要觀察哪一種變動類型,需要在options對象中指定為true即可;但是如果設置subtree的變動,必須同時指定childList, attributes 和 characterData 中的一種或多種。
1. 監測childList的變動;
如下demo代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ console.log(mutations); console.log(instance); mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { childList: true, subtree: true }); list.appendChild(document.createElement('div')); list.appendChild(document.createTextNode('foo')); list.removeChild(list.childNodes[0]); list.childNodes[0].appendChild(document.createElement('div')); </script> </html>
如上代碼 在控制台上打印信息如下,我們打印第一個回調參數 mutations 后,截圖如下:
2. 監測characterData的變動
代碼如下:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { childList:true, characterData:true, subtree:true }); list.childNodes[0].data = "cha"; </script> </html>
3. 監測屬性的變動;
代碼如下:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { attributes: true }); // 設置節點的屬性 會觸發回調函數 list.setAttribute('data-value', '111'); // 重新設置屬性 會觸發回調 list.setAttribute('data-value', '2222'); // 刪除屬性 也會觸發回調 list.removeAttribute('data-value'); </script> </html>
如下圖所示:
除了基本的變動類型之外,options對象還可以設定以下屬性
attributeOldValue: {boolean} 表示觀察attributes變動時,是否需要記錄變動前的屬性值。
characterDataOldValue: {boolean} 表示觀察characterData變動時,是否需要記錄變動前的值。
attributeFilter {Array} 表示需要觀察的特定屬性 比如 ['class', 'src']
1. attributeOldValue 屬性變動前,是否需要記錄變動之前的值; 代碼如下:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { attributes: true, attributeOldValue: true }); // 設置節點的屬性 會觸發回調函數 list.setAttribute('data-value', '111'); // 刪除屬性 list.setAttribute('data-value', '2222'); </script> </html>
如上截圖的oldValue 就是變動之前的值
2. characterData變動時,是否需要記錄變動前的值。
如下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { childList:true, characterData:true, subtree:true, characterDataOldValue: true }); // 設置數據 觸發回調 list.childNodes[0].data = "aaa"; // 重新設置數據 重新觸發回調 list.childNodes[0].data = "bbbb"; </script> </html>
第一次設置數據,記錄變動前的數據如下:
第二次設置數據,記錄變動前的回調如下:
可以看到oldValue 的變動值;
attributeFilter {Array} 表示需要觀察的特定屬性 比如 ['class', 'src'];代碼如下:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); }); }); Observer.observe(list, { attributes: true, attributeFilter: ['data-value'] }); // 第一次設置屬性 data-key 不會觸發的,因為data-value 不存在 list.setAttribute('data-key', 1); // 第二次會觸發 list.setAttribute('data-value', 1); </script> </html>
下面我們做一個簡單的demo編輯器,首先給父級元素 ol 設置 contenteditable 讓容器可編輯,然后構造一個observer 監聽子元素的變化,每次回車的時候,控制台輸出它的內容;如下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol contenteditable oninput="" style='border: 1px solid red'> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ console.log(mutation); if(mutation.type === 'childList') { var list_values = [].slice.call(list.children).map(function(node) { return node.innerHTML; }).filter(function(s) { if(s === '<br>') { return false; } else { return true; } }); console.log(list_values); } }); }); Observer.observe(list, { childList: true }); </script> </html>
現在我們繼續可以做一個類似於 input和textarea中的 valueChange的事件一樣的,監聽值變化,之前的值和之后的值,如下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='demo'> <ol contenteditable oninput="" style='border: 1px solid red'> <li>111111</li> </ol> </div> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var list = document.querySelector('ol'); var Observer = new MutationObserver(function(mutations, instance){ mutations.forEach(function(mutation){ var 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, }); </script> </html>
注意: 對input 和 textarea 不起作用的。
編輯器統計字數的demo
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id='editor' contenteditable style="width: 240px; height: 80px; border: 1px solid red;"></div> <p id="textInput">還可以輸入100字</p> </body> <script type="text/javascript"> var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var editor = document.querySelector('#editor'); var textInput = document.querySelector('#textInput'); var observer = new MutationObserver(function(mutations){ mutations.forEach(function(mutation) { if(mutation.type === 'characterData') { var newValue = mutation.target.textContent; textInput.innerHTML = "還可以輸入" + (1000 - newValue.length+"字"); } }); }); observer.observe(editor, { childList: true, attributes: true, characterData: true, subtree: true, characterDataOldValue: true, }); </script> </html>