JQuery或纯Javascript中的DOM Mutation事件

24

是否存在 JQuery 或原生 JavaScript 中可跨浏览器触发的 DOM 变化事件?

为了澄清,假设我在页面上有一个脚本,它将一个 div 插入到 body 中。我无法访问该脚本,也不知道何时插入了该 div。我想知道是否有 DOM 变化事件可以添加监听器来知道何时插入了元素。我知道我可以使用定时器周期性地检查插入情况,但我不喜欢这样做会带来的开销。


1
我认为所有的jQuery事件都可以跨浏览器触发... - Peter Olson
1
相关:http://lists.w3.org/Archives/Public/public-webapps/2011AprJun/1318.html - Šime Vidas
我希望得到比“没有”更好的答案 :-P - aziz punjani
1
这样的问题引发了一个为什么。这让我怀疑是否存在ajax调用和一些显示操作。在这种情况下,有其他解决方案来解决这种问题。否则,如果您知道自己在做什么并且原因,我建议作为一种简单的解决方案定期检查body节点的长度。 - Olivvv
1
整个问题在于我无法控制将数据注入正文的脚本。如果我能够控制,显而易见的解决方案就是添加回调函数。 - aziz punjani
显示剩余7条评论
6个回答

24

这确实是一种hack方法,但为什么不修补用于插入节点的底层DOM方法呢?有几种方法可以实现:

A. 如果您知道将要附加到哪个具体元素:

var c = document.getElementById('#container');
c.__appendChild = c.appendChild;
c.appendChild = function(){
     alert('new item added');
     c.__appendChild.apply(c, arguments); 
}

这里有一个演示A的JSFiddle。

B。你知道将要添加到哪种类型的元素中:

HTMLDivElement.prototype.__appendChild = HTMLDivElement.prototype.appendChild;
HTMLDivElement.prototype.appendChild = function(){
    alert('new item added');
    HTMLDivElement.prototype.__appendChild(this,arguments); 
}

这是B的演示示例

(请注意,解决方案B不支持IE < 8或任何不支持DOM原型的其他浏览器。)

同样的技术也可以很容易地用于所有基础的DOM变异函数,如insertBeforereplaceChildremoveChild

这是一个通用的思路,这些演示可以适应几乎任何其他用例——比如说您想铺设广泛的网并捕获所有添加,而不管类型并且确保它在IE < 8以外的其他浏览器上工作吗?(请参见下面的C示例)


更新

C. 递归遍历DOM,在每个元素上交换函数以触发回调,然后对任何正在附加的子级应用相同的补丁。

var DOMwatcher = function(root, callback){
  var __appendChild = document.body.appendChild;

  var patch = function(node){
    if(typeof node.appendChild !== 'undefined' && node.nodeName !== '#text'){
      node.appendChild = function(incomingNode){
        callback(node, incomingNode);
        patch(incomingNode);
        walk(incomingNode);
        __appendChild.call(node, incomingNode);
      };
    }
    walk(node);  
  };

  var walk = function(node){
    var i = node.childNodes.length;
    while(i--){
      patch(node.childNodes[i]);
    }
  };

  patch(root);

};

DOMwatcher(document.body, function(targetElement, newElement){ 
   alert('append detected');    
});

$('#container ul li').first().append('<div><p>hi</p></div>');
$('#container ul li div p').append('<a href="#foo">bar</a>');

这里有一个展示C的演示。

更新2

正如Tim Down评论所说,由于appendChild不是一个函数且不支持callapply,上面提到的解决方案在IE < 8中也无法工作。如果typeof document.body.appendChild !== 'function',您可以始终退回到笨重但可靠的setInterval方法。


1
这在IE < 9中不起作用,而且通常很危险,因为主机方法(如appendChild())不需要是“Function”对象(在IE < 9中确实不是)。 - Tim Down
1
@TimDown 拍得好!你是对的,在appendChild不是一个Function的浏览器中,这个解决方案将会失败,因为它不响应callapply。很好地发现了这一点,我已经更新了答案以反映这一点。然而,你会发现上面的代码在IE 8中确实可以工作。 - Daniel Mendel
啊,好的。我承认我对IE 8不确定。 - Tim Down
如果您正在寻找此方法的替代方案,这里是http://www.jqui.net/jquery-projects/jquery-mutate-official/,它也可以让您自己添加事件并监视几乎任何更改、类名更改、高度更改、宽度更改。 - Val
迷人而可怕。+1! - Fresheyeball

8

3
IE 9是第一个拥有DOM变异事件的IE版本,因此在IE <9中无法使用此功能。虽然这些事件已被弃用,但它们的替代规范尚未完整,因此它们不会很快消失。 - Tim Down
太好了解到IE的相关信息,因为我并不常用它。顺便说一句,离题了:昨天我发现了你的Rangy库--真是太棒了,谢谢你。 :) - Kai

4
JCADE(JQuery创建和销毁事件)可以实现此功能。它在IE中使用“行为”(behaviors),在其他浏览器中使用“DOM突变事件”(DOM mutation events)。它不使用定时事件,也不包装类似于livequery的JQuery方法。 https://github.com/snesin/jcade
    $( document ).create( "a", function( event ) {
      alert( event.target );
    });


3

1
这看起来很有前途,但当我查看脚本代码时发现他们没有使用jQuery将div插入DOM中。 - aziz punjani
嗯,真糟糕。我得承认,我尚未尝试过这个,所以我无法探讨它们的工作原理。抱歉。 - Adam Terlson

0

可以使用livequery插件来实现此功能。在1.x版本中,定时器被用于定期检查任何更改。然而,更新的2.x分支似乎可以在没有定期超时的情况下工作(未经测试)。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接