41

根据 spec,只有BODYandFRAMESET元素提供了一个“onload”事件来附加,但我想知道一个动态创建的 DOM 元素何时被添加到 JavaScript 中的 DOM 中。

我目前正在使用的超幼稚启发式方法如下:

  • 向后遍历元素的parentNode属性,直到找到最终的祖先(即parentNode.parentNode.parentNode.etc直到parentNode为null)

  • 如果最终祖先具有已定义的非空主体属性

    • 假设有问题的元素是 dom 的一部分
  • 别的

    • 在 100 毫秒内再次重复这些步骤

我所追求的要么是确认我所做的是足够的(同样,它在 IE7 和 FF3 中都有效),要么是一个更好的解决方案,无论出于何种原因,我都完全没有注意到;也许我应该检查其他属性等。


编辑:我想要一种与浏览器无关的方式,不幸的是,我并不生活在一个浏览器的世界中;也就是说,感谢您提供特定于浏览器的信息,但请注意您知道它可以在哪个浏览器运行。谢谢!

4

11 回答 11

30

更新:对于任何对此感兴趣的人,这是我最终使用的实现:

function isInDOMTree(node) {
   // If the farthest-back ancestor of our node has a "body"
   // property (that node would be the document itself), 
   // we assume it is in the page's DOM tree.
   return !!(findUltimateAncestor(node).body);
}
function findUltimateAncestor(node) {
   // Walk up the DOM tree until we are at the top (parentNode 
   // will return null at that point).
   // NOTE: this will return the same node that was passed in 
   // if it has no ancestors.
   var ancestor = node;
   while(ancestor.parentNode) {
      ancestor = ancestor.parentNode;
   }
   return ancestor;
}

我想要这样做的原因是提供一种onload为 DOM 元素合成事件的方法。这是那个函数(虽然我使用的东西略有不同,因为我将它与MochiKit结合使用):

function executeOnLoad(node, func) {
   // This function will check, every tenth of a second, to see if 
   // our element is a part of the DOM tree - as soon as we know 
   // that it is, we execute the provided function.
   if(isInDOMTree(node)) {
      func();
   } else {
      setTimeout(function() { executeOnLoad(node, func); }, 100);
   }
}

例如,可以按如下方式使用此设置:

var mySpan = document.createElement("span");
mySpan.innerHTML = "Hello world!";
executeOnLoad(mySpan, function(node) { 
   alert('Added to DOM tree. ' + node.innerHTML);
});

// now, at some point later in code, this
// node would be appended to the document
document.body.appendChild(mySpan);

// sometime after this is executed, but no more than 100 ms after,
// the anonymous function I passed to executeOnLoad() would execute

希望这对某人有用。

注意:我最终选择此解决方案而不是Darryl 的答案的原因是 getElementById 技术仅在您位于同一文档中时才有效;我在页面上有一些 iframe,页面之间以一些复杂的方式相互通信 - 当我尝试这个时,问题是它找不到元素,因为它是与它正在执行的代码不同的文档的一部分.

于 2009-05-12T03:10:34.650 回答
21

最直接的答案是使用Node.containsChrome、Firefox (Gecko)、Internet Explorer、Opera 和 Safari 支持的方法。这是一个例子:

var el = document.createElement("div");
console.log(document.body.contains(el)); // false
document.body.appendChild(el);
console.log(document.body.contains(el)); // true
document.body.removeChild(el);
console.log(document.body.contains(el)); // false

理想情况下,我们会使用document.contains(el),但这在 IE 中不起作用,所以我们使用document.body.contains(el).

不幸的是,您仍然需要轮询,但检查一个元素是否在文档中非常简单:

setTimeout(function test() {
  if (document.body.contains(node)) {
    func();
  } else {
    setTimeout(test, 50);
  }
}, 50);

如果您可以在页面中添加一些 CSS,这是另一种使用动画检测节点插入的巧妙技术:http ://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

于 2012-12-05T14:57:11.840 回答
9

无需将 DOM 树向上移动到文档元素,只需使用element.ownerDocument. 看到这里:https ://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument 并这样做:

element.ownerDocument.body.contains(element)

你很好。

于 2013-04-06T00:18:42.487 回答
8

你能不做document.getElementById('newElementId');,看看是否返回true。如果没有,就像你说的,等待 100 毫秒再试一次?

于 2008-10-20T22:53:14.713 回答
2

您可以查询document.getElementsByTagName("*").length或创建自定义 appendChild 函数,如下所示:

var append = function(parent, child, onAppend) {
  parent.appendChild(child);
  if (onAppend) onAppend(child);
}

//inserts a div into body and adds the class "created" upon insertion
append(document.body, document.createElement("div"), function(el) {
  el.className = "created";
});

更新

根据要求,将我评论中的信息添加到我的帖子中

John Resig今天在 Ajaxian 上对 Peppy 库发表了评论,似乎暗示他的 Sizzle 库可能能够处理 DOM 插入事件。我很想知道是否也会有处理 IE 的代码

按照轮询的想法,我读到某些元素属性在元素被附加到文档之前不可用(例如 element.scrollTop),也许您可​​以轮询它而不是执行所有 DOM 遍历。

最后一件事:在 IE 中,可能值得探索的一种方法是使用 onpropertychange 事件。我认为将其附加到文档中必然会触发至少一个属性的该事件。

于 2008-10-21T05:51:42.403 回答
2

在一个完美的世界中,您可以挂钩突变事件。我怀疑它们即使在标准浏览器上也能可靠地工作。听起来您已经实现了一个突变事件,因此您可以添加它以使用本机突变事件,而不是在支持这些事件的浏览器上进行超时轮询。

我已经看到 DOM-change 实现通过与document.body.innerHTMLlast saved 进行比较来监控更改.innerHTML,这并不完全优雅(但有效)。由于您最终要检查是否已添加特定节点,因此最好只检查每个中断。

我能想到的改进是使用.offsetParent而不是.parentNode因为它可能会使一些父母脱离你的循环(看评论)。或者compareDocumentIndex()在除 IE 之外的所有设备上使用并测试 IE.sourceIndex的属性(如果节点不在 DOM 中,则应为 -1)。

这也可能有帮助:John Resig 的 X 浏览器 compareDocumentIndex 实现

于 2008-10-21T16:07:30.367 回答
2

MutationObserver用于检测元素何时添加到 DOM。现在,所有现代浏览器(Chrome 26+、Firefox 14+、IE11、Edge、Opera 15+ 等)都广泛支持 MutationObservers。

这是一个简单的示例,说明如何使用 aMutationObserver来侦听何时将元素添加到 DOM。

为简洁起见,我使用 jQuery 语法来构建节点并将其插入 DOM。

var myElement = $("<div>hello world</div>")[0];

var observer = new MutationObserver(function(mutations) {
   if (document.contains(myElement)) {
        console.log("It's in the DOM!");
        observer.disconnect();
    }
});

observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});

$("body").append(myElement); // console.log: It's in the DOM!

observer每当从document. 在处理程序内部,我们然后执行contains检查以确定myElement现在是否在document.

您不需要遍历存储的每个 MutationRecord,mutations因为您可以document.contains直接在myElement.

要提高性能,请替换document为将包含myElement在 DOM 中的特定元素。

于 2018-01-30T06:07:07.767 回答
1

您想要DOMNodeInserted事件(或DOMNodeInsertedIntoDocument)。

编辑:IE 完全有可能不支持这些事件,但我现在无法测试。

于 2008-10-20T22:42:01.147 回答
0

这是从 jQuery 代码中提取的问题的另一个解决方案。以下函数可用于检查节点是否附加到 DOM 或是否“断开连接”。

功能已断开连接(节点){
    返回!节点 || !node.parentNode || node.parentNode.nodeType === 11;
}

一个 nodeType === 11 定义了一个 DOCUMENT_FRAGMENT_NODE,它是一个没有连接到文档对象的节点。

有关更多信息,请参阅 John Resig 的以下文章:http: //ejohn.org/blog/dom-documentfragments/

于 2010-03-10T00:29:33.520 回答
0
var finalElement=null; 

我在循环中构建 dom 对象,当循环处于最后一个循环并且lastDomObject然后创建时:

 finalElement=lastDomObject; 

离开循环:

while (!finalElement) { } //this is the delay... 

下一个动作可以使用任何新创建的 dom 对象。它在第一次尝试时起作用。

于 2013-04-11T00:28:08.360 回答
-1

IE 中不支持DOMNodeInserted或DOMNodeInsertedIntoDocument事件。DOM 的 IE [only] 功能中缺少的另一个功能是compareDocumentPosition函数,该函数旨在执行其名称所暗示的操作。

至于建议的解决方案,我认为没有更好的解决方案(除非你做了一些肮脏的技巧,比如覆盖原生 DOM 成员实现)

另外,更正问题的标题:动态创建的元素从创建的那一刻起就是 DOM 的一部分,它可以附加到文档片段或另一个元素,但它可能不会附加到文档。

我还认为您描述的问题不是您遇到的问题,这看起来是您问题的潜在解决方案之一。你能详细解释一下用例吗?

于 2008-10-20T23:03:28.780 回答