MutationObserver介紹
MutationObserver給開發者們提供了一種能在某個范圍內的DOM樹發生變化時作出適當反應的能力.該API設計用來替換掉在DOM3事件規范中引入的Mutation事件.
MDN的資料:MutationObserver
MutationObserver是一個構造函數, 所以創建的時候要通過 new MutationObserver;
實例化MutationObserver的時候需要一個回調函數,該回調函數會在指定的DOM節點(目標節點)發生變化時被調用,
在調用時,觀察者對象會傳給該函數兩個參數:
1:第一個參數是個包含了若干個MutationRecord對象的數組; 2:第二個參數則是這個觀察者對象本身.
比如這樣:
var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { console.log(mutation.type); }); });
observer的方法
實例observer有三個方法: 1: observe ;2: disconnect ; 3: takeRecords ;
observe方法
observe方法:給當前觀察者對象注冊需要觀察的目標節點,在目標節點(還可以同時觀察其后代節點)發生DOM變化時收到通知;
這個方法需要兩個參數,第一個為目標節點, 第二個參數為需要監聽變化的類型,是一個json對象, 實例如下:
observer.observe( document.body, { 'childList': true, //該元素的子元素新增或者刪除 'subtree': true, //該元素的所有子元素新增或者刪除 'attributes' : true, //監聽屬性變化 'characterData' : true, // 監聽text或者comment變化 'attributeOldValue' : true, //屬性原始值 'characterDataOldValue' : true });
disconnect方法
disconnect方法會停止觀察目標節點的屬性和節點變化, 直到下次重新調用observe方法;
takeRecords
清空觀察者對象的
記錄隊列,并返回一個數組, 數組中包含Mutation事件對象;
MutationObserver實現一個編輯器的redo和undo再適合不過了, 因為每次指定節點內部發生的任何改變都會被記錄下來, 如果使用傳統的keydown或者keyup實現會有一些弊端,比如:
1:失去滾動, 導致滾動位置不準確; 2:失去焦點; ....
用了幾小時的時間,寫了一個通過MutationObserver實現的undo和redo(撤銷回退的管理)的管理插件MutationJS, 可以作為一個單獨的插件引入:(http://files.cnblogs.com/files/diligenceday/MutationJS.js):

/** * @desc MutationJs, 使用了DOM3的新事件 MutationObserve; 通過監聽指定節點元素, 監聽內部dom屬性或者dom節點的更改, 并執行相應的回調; * */ window.nono = window.nono || {}; /** * @desc * */ nono.MutationJs = function( dom ) { //統一兼容問題 var MutationObserver = this.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; //判斷瀏覽器是或否支持MutationObserver; this.mutationObserverSupport = !!MutationObserver; //默認監聽子元素, 子元素的屬性, 屬性值的改變; this.options = { 'childList': true, 'subtree': true, 'attributes' : true, 'characterData' : true, 'attributeOldValue' : true, 'characterDataOldValue' : true }; //這個保存了MutationObserve的實例; this.muta = {}; //list這個變量保存了用戶的操作; this.list = []; //當前回退的索引 this.index = 0; //如果沒有dom的話,就默認監聽body; this.dom = dom|| document.documentElement.body || document.getElementsByTagName("body")[0]; //馬上開始監聽; this.observe( ); }; $.extend(nono.MutationJs.prototype, { //節點發生改變的回調, 要把redo和undo都保存到list中; "callback" : function ( records , instance ) { //要把索引后面的給清空; this.list.splice( this.index+1 ); var _this = this; records.map(function(record) { var target = record.target; console.log(record); //刪除元素或者是添加元素; if( record.type === "childList" ) { //如果是刪除元素; if(record.removedNodes.length !== 0) { //獲取元素的相對索引; var indexs = _this.getIndexs(target.children , record.removedNodes ); _this.list.push({ "undo" : function() { _this.disconnect(); _this.addChildren(target, record.removedNodes ,indexs ); _this.reObserve(); }, "redo" : function() { _this.disconnect(); _this.removeChildren(target, record.removedNodes ); _this.reObserve(); } }); //如果是添加元素; }; if(record.addedNodes.length !== 0) { //獲取元素的相對索引; var indexs = _this.getIndexs(target.children , record.addedNodes ); _this.list.push({ "undo" : function() { _this.disconnect(); _this.removeChildren(target, record.addedNodes ); _this.reObserve(); }, "redo" : function () { _this.disconnect(); _this.addChildren(target, record.addedNodes ,indexs); _this.reObserve(); } }); }; //@desc characterData是什么鬼; //ref : http://baike.baidu.com/link?url=Z3Xr2y7zIF50bjXDFpSlQ0PiaUPVZhQJO7SaMCJXWHxD6loRcf_TVx1vsG74WUSZ_0-7wq4_oq0Ci-8ghUAG8a }else if( record.type === "characterData" ) { var oldValue = record.oldValue; var newValue = record.target.textContent //|| record.target.innerText, 不準備處理IE789的兼容,所以不用innerText了; _this.list.push({ "undo" : function() { _this.disconnect(); target.textContent = oldValue; _this.reObserve(); }, "redo" : function () { _this.disconnect(); target.textContent = newValue; _this.reObserve(); } }); //如果是屬性變化的話style, dataset, attribute都是屬于attributes發生改變, 可以統一處理; }else if( record.type === "attributes" ) { var oldValue = record.oldValue; var newValue = record.target.getAttribute( record.attributeName ); var attributeName = record.attributeName; _this.list.push({ "undo" : function() { _this.disconnect(); target.setAttribute(attributeName, oldValue); _this.reObserve(); }, "redo" : function () { _this.disconnect(); target.setAttribute(attributeName, newValue); _this.reObserve(); } }); }; }); //重新設置索引; this.index = this.list.length-1; }, "removeChildren" : function ( target, nodes ) { for(var i= 0, len= nodes.length; i<len; i++ ) { target.removeChild( nodes[i] ); }; }, "addChildren" : function ( target, nodes ,indexs) { for(var i= 0, len= nodes.length; i<len; i++ ) { if(target.children[ indexs[i] ]) { target.insertBefore( nodes[i] , target.children[ indexs[i] ]) ; }else{ target.appendChild( nodes[i] ); }; }; }, //快捷方法,用來判斷child在父元素的哪個節點上; "indexOf" : function ( target, obj ) { return Array.prototype.indexOf.call(target, obj) }, "getIndexs" : function (target, objs) { var result = []; for(var i=0; i<objs.length; i++) { result.push( this.indexOf(target, objs[i]) ); }; return result; }, /** * @desc 指定監聽的對象 * */ "observe" : function( ) { if( this.dom.nodeType !== 1) return alert("參數不對,第一個參數應該為一個dom節點"); this.muta = new this.MutationObserver( this.callback.bind(this) ); //馬上開始監聽; this.muta.observe( this.dom, this.options ); }, /** * @desc 重新開始監聽; * */ "reObserve" : function () { this.muta.observe( this.dom, this.options ); }, /** *@desc 不記錄dom操作, 所有在這個函數內部的操作不會記錄到undo和redo的列表中; * */ "without" : function ( fn ) { this.disconnect(); fn&fn(); this.reObserve(); }, /** * @desc 取消監聽; * */ "disconnect" : function () { return this.muta.disconnect(); }, /** * @desc 保存Mutation操作到list; * */ "save" : function ( obj ) { if(!obj.undo)return alert("傳進來的第一個參數必須有undo方法才行"); if(!obj.redo)return alert("傳進來的第一個參數必須有redo方法才行"); this.list.push(obj); }, /** * @desc ; * */ "reset" : function () { //清空數組; this.list = []; this.index = 0; }, /** * @desc 把指定index后面的操作刪除; * */ "splice" : function ( index ) { this.list.splice( index ); }, /** * @desc 往回走, 取消回退 * */ "undo" : function () { if( this.canUndo() ) { this.list[this.index].undo(); this.index--; }; }, /** * @desc 往前走, 重新操作 * */ "redo" : function () { if( this.canRedo() ) { this.index++; this.list[this.index].redo(); }; }, /** * @desc 判斷是否可以撤銷操作 * */ "canUndo" : function () { return this.index !== -1; }, /** * @desc 判斷是否可以重新操作; * */ "canRedo" : function () { return this.list.length-1 !== this.index; } });
MutationJS如何使用
那么這個MutationJS如何使用呢?
//這個是實例化一個MutationJS對象, 如果不傳參數默認監聽body元素的變動;
mu = new nono.MutationJs();
//可以傳一個指定元素,比如這樣;
mu = new nono.MutationJS( document.getElementById("div0") );
//那么所有該元素下的元素變動都會被插件記錄下來;
Mutation的實例mu有幾個方法:
1:mu.undo() 操作回退;
2:mu.redo() 撤銷回退;
3:mu.canUndo() 是否可以操作回退, 返回值為true或者false;
4:mu.canRedo() 是否可以撤銷回退, 返回值為true或者false;
5:mu.reset() 清空所有的undo列表, 釋放空間;
6:mu.without() 傳一個為函數的參數, 所有在該函數內部的dom操作, mu不做記錄;
MutationJS實現了一個簡易的undoManager提供參考,在火狐和chrome,谷歌瀏覽器,IE11上面運行完全正常:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script src="http://cdn.bootcss.com/jquery/1.9.0/jquery.js"></script> <script src="http://files.cnblogs.com/files/diligenceday/MutationJS.js"></script> </head> <body> <div> <p> MutationObserver是為了替換掉原來Mutation Events的一系列事件, 瀏覽器會監聽指定Element下所有元素的新增,刪除,替換等; </p> <div style="padding:20px;border:1px solid #f00"> <input type="button" value="撤銷操作" id="prev">; <input type="button" value="撤銷操作回退" id="next">; </div> <input type="button" value="添加節點" id="b0">; <input value="text" id="value"> <div id="div"></div> </div> <script> window.onload = function () { window.mu = new nono.MutationJs(); //取消監聽 mu.disconnect(); //重新監聽 mu.reObserve(); document.getElementById("b0").addEventListener("click", function ( ev ) { div = document.createElement("div"); div.innerHTML = document.getElementById("value").value; document.getElementById("div").appendChild( div ); }); document.getElementById("prev").addEventListener("click", function ( ev ) { mu.undo(); }); document.getElementById("next").addEventListener("click", function ( ev ) { mu.redo(); }); }; </script> </body> </html>
DEMO在IE下的截圖:
MutatoinObserver的瀏覽器兼容性:
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support |
18 webkit |
14 (14) | 11 | 15 | 6.0 WebKit |
MDN的資料:MutationObserver
作者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329
文章列表