注:增加更新mutation observer相关的内容/表格内容简单进行了翻译@20140523
很多时候,我们都会遇到一个头疼的问题,如何监听页面模块DOM的变化。比如页面模块使用了类似datalazyload这样的组件,什么时候能知道这个模块的内容渲染成dom,并可以对其进行操作。
通常的解决方案都是datalazyload本身实例化的时候,提供一些callback处理。
类似:
var D = new DataLazyload();
D.addCallback('moduleId', callback);
D.on('module-load-event:moduleId', callback);
前提是,你能拿到DataLazyload的实例。
还有没有别的方式来解决这个问题呢?
这个时候,可以看下浏览器做了哪些事情。
<table> <tbody> <tr> <td>事件名</td> <td>规范地址</td> <td>描述</td> </tr> <tr> <td>DOMActivate</td> <td>W3C Draft</td> <td>button、a链接等元素状态变化时触发。</td> </tr> <tr> <td>DOMAttrModified</td> <td>W3C Draft</td> <td>节点attr属性发生变化时触发</td> </tr> <tr> <td>DOMAttributeNameChanged</td> <td>W3C Draft</td> <td>节点的namespaceURI或者nodeName变化时触发(document.renameNode()这样古老的方法)</td> </tr> <tr> <td>DOMCharacterDataModified</td> <td>W3C Draft</td> <td>节点内部文本变化时触发</td> </tr> <tr> <td>DOMContentLoaded</td> <td>HTML5</td> <td> domready</td> </tr> <tr> <td>DOMElementNameChanged</td> <td>W3C Draft</td> <td>element节点namespaceURI或者nodeName变化时触发(document.renameNode()这样古老的方法)</td> </tr> <tr> <td>DOMFocusIn</td> <td>W3C Draft</td> <td> 节点出现focus后触发。</td> </tr> <tr> <td>DOMFocusOut</td> <td>W3C Draft</td> <td>节点出现focusout后触发</td> </tr> <tr> <td>DOMNodeInserted</td> <td>W3C Draft</td> <td>发生节点插入时触发</td> </tr> <tr> <td>DOMNodeInsertedIntoDocument</td> <td>W3C Draft</td> <td>文档中出现节点插入时触发</td> </tr> <tr> <td>DOMNodeRemoved</td> <td>W3C Draft</td> <td>节点本身从父节点中移除时触发</td> </tr> <tr> <td>DOMNodeRemovedFromDocument</td> <td>W3C Draft</td> <td>文档中出现节点移除时触发</td> </tr> <tr> <td>DOMSubtreeModified</td> <td>W3C Draft</td> <td>子节点产生修改时触发</td> </tr> <tr> <td></td> </tr> </tbody> </table>
表格资料来自: MDN documentation.
其中可以看到最熟悉的DOMContentLoaded,也就是标准的domready事件。
下面是一个DOMSubtreeModified相关的例子
// 监听document内部dom树的变化,比如插入/删除节点等
document.addEventListener("DOMSubtreeModified", function(e) {
// 有变化时触发
console.warn("change!", e);
}, false);
// 往body里插入节点的测试
var a = document.createElement("a");
document.body.appendChild(a);
/*
输出:
{
ADDITION: 2,
MODIFICATION: 1,
REMOVAL: 3,
attrChange: 0,
attrName: "",
defaultPrevented: false,
newValue: "",
prevValue: "",
relatedNode: null,
initMutationEvent: initMutationEvent(),
bubbles: true,
cancelable: false,
constructor: MutationEvent { MODIFICATION=1, ADDITION=2, REMOVAL=3},
currentTarget: Document en,
eventPhase: 3,
explicitOriginalTarget: body.home,
isTrusted: true,
originalTarget: body.home,
target: body.home,
timeStamp: 0,
type: "DOMSubtreeModified"
}
*/
还可以监听节点属性的改变。
document.getElementById("slideshowImage").addEventListener("DOMAttrModified", function(e) {
console.warn(e.attrName + " changed from ", e.prevValue," to: ", e.newValue);
}, false);
attrName对应熟悉名,preValue对应修改前的属性,newValue对应修改后的属性。
一切都看起来很美好,可惜这个规范已经被抛弃了,原本已经实现这些事件的浏览器也渐渐不再支持。
原因很简单,类似el.innerHTML = 'xxx',这样的操作,有多少个元素在这次操作中被删除,就会触发多少次remove事件,这本身对浏览器就是一个巨大的负担。
绕了一圈回来之后,似乎只有使用一些愚蠢一些的替代方案了。
比如我们要对一个元素进行操作的时候,需要知道这个元素是否已经渲染完成。
或者我们可能会需要对A元素的子元素进行操作,那么,我们就需要知道A元素的子元素是否可以操作了。
available: function (id, callback) {
var loopStopFlag = false;
setTimeout(function () {
loopStopFlag = true;
}, 5000);
setTimeout(function () {
if (doc.getElementById(id)) {
callback();
} else if (!loopStopFlag) {
setTimeout(arguments.callee, 50);
}
}, 50)
}
而对于元素属性修改的监听,就只能基于浏览器api再进行一层包装。
var attr = function(el, attrKey, attrValue){
var attrName = attrKey; //属性名
var prevValue = el.getAttribute('attrKey'); //旧值
var newValue = attrValue; //新值
//xxx
}
这个时候,统一编码规范的重要性就体现出来了,通过使用统一提供的API,我们一样能实现对各个操作的监听,比如jQuery如果愿意做的话,也是可以轻易实现这些操作的监听的。
笔者知识面有限,如果你有这方面相关的更好的想法,欢迎讨论。
微博中有同学提醒,还可以用mutation observer来监听dom树的变化。
恰巧云飞同学已经把MDN里的mutation observer部分的文档汉化了。
首先,我们需要看下,为什么规范抛弃了mutation events之后,又搞了一个mutation observer。
看MDN里对mutation observer构造函数的定义:
MutationObserver(
function callback
);
<dl style="color: #4d4e53;">
<dt>callback
</dt>
<dd>该回调函数会在指定的DOM节点(目标节点)发生变化时被调用.在调用时,观察者对象会传给该函数两个参数,第一个参数是个包含了若干个MutationRecord
对象的数组,第二个参数则是这个观察者对象本身.</dd>
</dl>
看起来新的规范实现了类似事件代理类似的方式,统一进行触发,而不是以节点为单位触发。
之前提到mutation events的缺点就是,一次innerHTML导致1000个节点被删除的时候,会触发1000次remove事件。
而mutation observer则实现了一次操作,触发一次DOM变化事件,并把这次操作相关的DOM修改统一返回。
简单的例子:
var observer = new MutationObserver(function(records){
// records修改记录
});
var options = {
'childList': true,
'attributes':true
} ;
observer.observe( document.body, options );
records里包含了修改所在的节点相关信息:
关于mutation observer相关的资料,除了MDN之外,还建议看下JavaScript标准参考教程-Mutation Observer
参考资料
扫码关注w3ctech微信公众号
支持小豪!
支持
共收到2条回复