为什么向尚未在DOM中的元素添加内容比使用JavaScript片段更快?

6
考虑以下三种向
    中添加
  • 元素的方式:
    朴素版(速度慢20%):
    var ul = document.getElementById('targetUl');
    
    for (var i = 0; i < 200; i++) {
      var li = document.createElement('li');
      li.innerHTML = Math.random();
      ul.appendChild(li);
    }
    

    使用JavaScript片段(速度慢4%):
    var ul = document.getElementById('targetUl'),
        fragment = document.createDocumentFragment();
    
    for (var i = 0; i < 200; i++) {
      var li = document.createElement('li');
      li.innerHTML = Math.random();
      fragment.appendChild(li);
    }
    ul.appendChild(fragment);
    

    将内容添加到尚未在DOM中的元素(快1.26%):

    var ul = document.createElement('ul'),
        div = document.getElementById('targetDiv');
    
    for (var i = 0; i < 200; i++) {
      var li = document.createElement('li');
      li.innerHTML = Math.random();
      ul.appendChild(li);
    }
    div.appendChild(ul);
    

    为什么将DOM元素追加到内存中比将其追加到Fragment中更快?由于Fragment是为此目的而创建的,所以它不应该更快吗?除了在追加之前不需要包含顶级元素外,使用Fragment是否有任何优势?
    请查看jsperf的测试输出:http://jsperf.com/javascript-fragments-tests

不考虑底层代码或设计,无论你得到什么答案都是猜测。我的理解是,片段是任何DOM元素的通用容器,而UL则非常具体,只能有LI子元素,因此当元素被附加时,片段必须执行一些工作,而UL则不需要。我认为±4%不算显著。 - RobG
不要过于纠结于微观优化。 - Sterling Archer
什么能让 Fragment 更快?(假设性地询问与常规 DOM 元素相比,Fragment 可以进行哪些优化) - JKillian
@JKillian 这应该是一个最小化的文档对象,专门为这个特定的操作而设计。我不确定他们可以使用什么优化来实现这一点。 - agconti
2个回答

8
使用文档片段从中插入多个子元素(特别是当您的测试有200个子元素时)比插入单个父<ul>标签要更麻烦一些。
因此,使用片段时,您需要将200个<li>元素从片段重新分配到DOM中的<ul>标签中。
在上一个代码块中,您只需通过将其插入DOM来重新分配一个<ul>标签。
因此,在您的特定示例中,使用文档片段创建了更多的工作,以将其插入DOM,而不是运行最后一个示例的方式,该示例只需插入单个<ul>标签。
文档片段在您想要跟踪同一级别的大量元素并使用一行代码插入它们,并且父级已经存在于DOM中时具有其战术优势。 但是,与您可以将所有项从DOM收集到其实际父节点下的同一级别,然后仅插入该父节点的情况相比,它并不总是做事情的最快方法。

1
那么,为什么有人想要使用文档片段而不是创建您计划附加到文档的元素树的父节点呢?相比于创建父元素,文档片段有什么特别之处? - chiliNUT
3
有时候父节点已经存在于DOM中,你想要收集在DOM之外的子节点,然后一次性将它们全部插入。如果是这种情况,使用文档片段可以减少代码量,因为不使用文档片段意味着你要么必须跟踪一个子节点数组,要么创建自己的虚拟父节点,然后逐个地将所有子节点插入到DOM中。使用文档片段,则可以用一行代码将它们全部插入。在某些情况下,这是一种编程上的便利。 - jfriend00
我不确定我是否理解了你的回答。第二个测试比第三个测试做更多的工作,因为它必须重新父级化单个元素,即使该元素有许多子元素。如果我在片段中添加一个顶级元素,那么性能是否相同,因为那时它只需要重新父级化一个具有许多子元素的元素? - agconti
1
@agconti - 从片段中插入将具有与片段中顶级项数量成正比的性能,因为必须一次一个地重新父类化和插入那么多项到DOM中。那些顶级对象下面的子对象在插入时不会产生任何开销(除了进行一次重新布局时的布局复杂性),因为它们附加到其父对象,所以当移动父对象时,子对象也会自动移动(这是DOM层次结构的优势)。 - jfriend00
在Firefox中,DocumentFragment继承自FragmentOrElement,并添加了nsIDOMDocumentFragment接口。常规DOM元素实现更重的nsIDOMElement接口。因此,可能会有性能方面的影响,这将支持DocumentFragment,但如果实际上没有,我也不会感到惊讶。我知道这并不是什么明确的东西,但无论如何,查看FF源代码都是有趣和有益的:http://mxr.mozilla.org/mozilla-beta/source/content/base/public/ - JKillian
显示剩余4条评论

0

我的推论是...

对于Naive版本, 当在DOM中添加元素时,循环会变得越来越大。 随着更多的元素被加载到已经存在于DOM中的ul标签中,处理后继要附加的元素所需的时间也就越长。 这个过程需要更长的时间,因为你正在修改DOM中已经存在的标签。

对于JavaScript片段, 该片段在HTML文档中并不存在,只有在插入一系列li后才将其附加到ul中。 仅在循环部分进行后端处理,然后将包含多个li的片段插入到DOM中。当然,由于ul已经存在于DOM中,仍然涉及循环,因为采用子节点需要一些时间。

对于尚未在DOM中添加的元素, 这主要在后端上工作,并使UI解释器减少了努力,因为它就像在一堆文件上添加另一张纸(所有内容都在添加到文件堆的纸上完成)。

我的解释可能不够准确,但至少我分享了一个想法。这是我们游戏开发者中最大的问题之一。


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