DOMNodeInserted的替代方案

30

DOMNodeInserted已知会使动态页面变慢,MDN甚至建议完全不使用它,但没有提供任何替代方案。

我对被插入的元素不感兴趣,我只需要知道某些脚本何时修改了DOM。是否有比突变事件监听器更好的替代方案(也许是在nsiTimer内部使用getElementsByTagName)?


你需要整个DOM的这个功能吗?并且始终如一地需要吗? - Šime Vidas
Šime Vidas,是的,我需要,一些脚本在用户与页面交互时插入<object>标签。 - Fábio
@Fabio 那些脚本是第三方脚本还是你的脚本? - Šime Vidas
你对object还是整个DOM感兴趣? - Radu
我想知道我的DOM何时发生变化,以便我可以采取适当的行动。在我的情况下,我对插入页面中的媒体元素(<embed>,<object>,<video>)感兴趣。 - Fábio
显示剩余3条评论
6个回答

43
如果您正在创建一个面向最新移动手机和浏览器版本(Firefox 5+,Chrome 4+,Safari 4+,iOS Safari 3+,Android 2.1+)的Web应用程序,您可以使用以下代码创建一个令人惊叹的事件,用于插入DOM节点,它甚至可以在页面静态标记的初始节点上运行!
这里是完整带有示例的帖子链接:http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/ 关于Mutation Observers的说明:虽然较新的浏览器中的Mutation Observer功能非常适合监视DOM的简单插入和更改,但要理解此方法可用于做更多事情,因为它允许您监视任何CSS规则匹配。对于许多用例来说,这超级强大,因此我在这里将其打包成库:https://github.com/csuwildcat/SelectorListener 如果您想针对各种浏览器进行定位,您需要添加适当的CSS和animationstart事件名称前缀。您可以在上面链接的帖子中阅读更多相关信息。
基本节点插入情况:
CSS:
@keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

div.some-control {
    animation-duration: 0.01s;
    animation-name: nodeInserted;
}

JavaScript:

document.addEventListener('animationstart', function(event){
    if (event.animationName == 'nodeInserted'){
        // Do something here
    }
}, true);

监听更复杂的选择器匹配:

这使得一些使用变动观察者几乎不可能实现的功能成为可能。

CSS

@keyframes adjacentFocusSequence {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

.one + .two + .three:focus {
    animation-duration: 0.01s;
    animation-name: adjacentFocusSequence;
}

JavaScript:

document.addEventListener('animationstart', function(event){
    if (event.animationName == 'adjacentFocusSequence'){
        // Do something here when '.one + .two + .three' are 
        // adjacent siblings AND node '.three' is focused
    }
}, true);

5
这绝对是我见过的替代 DOMNodeInserted 最好的选择。事实上,我已经使用它几个月了,效果非常好。 - OpenGG
@naugtur 提出了一个好主意,特别是如果你只能在颜色上进行虚拟动画。我可能会用这个更新我的帖子和博客文章。 - csuwldcat
@naugtur,现在已经在Github上更新了,我已经调整了这个答案以匹配 - 感谢你的提醒! - csuwldcat
2
很抱歉要这么说,但现在你只是让人们感到困惑。你发布的代码远远不能跨浏览器使用(动画事件需要前缀,动画本身需要其他前缀),而且我从未说过你只能对颜色进行动画处理。这不是我测试过的内容。 - naugtur
@naugtur 我想这可能是一个误解,因为你最后的评论提到了我已经在答案中包含了你所说的确切的提示 -->“如果你想针对不同的浏览器,你需要在CSS和animationstart事件名称中添加适当的前缀。关于这方面的更多信息,你可以在上面链接的文章中阅读。” 这个提示从一开始就位于第一个代码块上方 ;) - csuwldcat
显示剩余11条评论

11

naugtur简要提到的一个新选择是MutationObserver。它被设计成替代已弃用的mutation事件,如果你开发的浏览器(如开发浏览器扩展)支持它。


1
然而,变异观察器并不提供基于CSS选择器的变化的通知,这是一个非常有用的功能。我有一个小模块,它包装了上面的关键帧解决方案,使CSS选择器的监听变得更加容易:https://github.com/csuwildcat/SelectorListener - csuwldcat
认为 @csuwldcat 的建议已经过时了 - 变异观察器可以检测到您可能从 CSS 选择器中推断出的任何更改(使用 attributes),以及更多内容(节点删除、文本节点更改)。 - Barney
@Barney 有几件事情需要注意:1)你可以使用这种方法监听选择器匹配,而使用Mutation Observers是不可能的 - 更多信息请参见:https://github.com/csuwildcat/SelectorListener,2)旧浏览器如IE9、Safari 5、6等不支持Mutation Observers。 - csuwldcat
@Barney 我已经更新了我的答案,并添加了另一个用例,可以清楚地说明这种方法可以轻松完成的事情,而Mutation Observers则无法做到。 - csuwldcat

0

0
尝试使用 customElements,但不要用于已创建的元素!
customElements.define( 'insertion-triggerable', class extends HTMLElement {
    connectedCallback() {
        console.log( 'connected' )
    }

    disconnectedCallback() {
        console.log( 'disconnected' )
    }

    adoptedCallback() {
        console.log( 'adopted' )
    }
} );

let element = document.createElement( 'insertion-triggerable' );

或者

customElements.define( 'insertion-triggerable', class extends HTMLDivElement {
    // --||--
}, { extends: 'div' } );

let divElement = document.createElement( 'div', { is: 'insertion-triggerable' } );

-1
这篇文章发布在这里是因为我在寻求关于DOMNodeInserted在IE9中失败的帮助时着陆到了这个问题,但请注意,此解决方案专门针对在使用End Request函数的ASP.NET上下文中使用jQuery的情况。您的结果可能会有所不同等等...

基本上,我们将放弃使用DOMNodeInserted并使用End Request来加载我们的事件处理程序:

旧代码:

$(document).ready(function() {

$(document).bind('DOMNodeInserted', function(event){
My jQuery event handler...

 }); 
});

===================================

新消息:

function Ajax_EndRequest {
  function2();
}

function2 (a,b){
  My jQuery event handler...
}

$(document).ready(function(){
  add_endRequest(Ajax_EndRequest); //this is what actually invokes the function upon request end.

 My jQuery event handler...//REMOVED -- don't need this now
});

-22
如果你只想在DOM改变时触发一个事件,可以像这样做:
var nodes=document.getElementsByTagName('*')||document.all;

function domchange(){
    alert('Hello');
}

window.setInterval(function(){
    var newnodes=document.getElementsByTagName('*')||document.all;
    if(newnodes!=nodes){
        nodes=newnodes;
        domchange();
    }
},1);

26
这是糟糕的代码,会破坏任何插入到其中的页面。 - user1385191
1
不是真的,但是非常好的评论。如果你想的话,可以将最后一个小数点“1”改为“500”。这样每半秒钟更新一次,而不是(最多)每秒1000次。 - Anonymous
13
到像msnbc.com这样的大型网站,记录getElementsByTagName("*")集合中有多少个元素。我得到了"2605"个元素。你不可能认为这是一个好主意。 - user1385191
哈哈哈,我刚刚登录只是为了向你们鼓掌...这些评论真的很棒。 - aefxx
是的,CSS中的*操作符本身就很昂贵。请不要再加上alert(...)操作。还有那个1毫秒的轮询,天哪! - Matt W

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