使用文档片段真的可以提高性能吗?

时间:2013-01-07 20:04:50

标签: javascript performance documentfragment

我对JS的表现有疑问。

说,我有下一个代码:

var divContainer = document.createElement("div"); divContainer.id="container";
var divHeader = document.createElement("div"); divHeader.id="header";
var divData = document.createElement("div"); divData.id="data";
var divFooter = document.createElement("div"); divFooter.id="footer";
divContainer.appendChild( divHeader );
divContainer.appendChild( divData );
divContainer.appendChild( divFooter );
document.getElementById("someElement").appendChild( divContainer );

这段代码只为其他一些函数创建了一个shell来创建一个网格,创建网格的过程非常复杂,并且有很多验证,目前我正在使用两种方法填充网格,一个创建整个html in一个数组变量,另一个创建元素并将它们附加到documentFragment

我的问题是,当我使用片段时,如果在使用片段时性能确实有所改善 - 他们管理内存中的元素,因此它们不会附加到文档中,因此,不会触发DOM重新计算和其他令人讨厌的东西。但是我创建变量的方式,在将容器附加到实际页面之前,它们不会附加到任何DOM元素。

所以我想知道前面的代码是否比使用包装它的文档片段具有更好的性能:

var fragment = document.createDocumentFragment();
var divContainer = document.createElement("div"); divContainer.id="container";
var divHeader = document.createElement("div"); divHeader.id="header";
var divData = document.createElement("div"); divData.id="data";
var divFooter = document.createElement("div"); divFooter.id="footer";
divContainer.appendChild( divHeader );
divContainer.appendChild( divData );
divContainer.appendChild( divFooter );
fragment.appendChild( divContainer )
document.getElementById("someElement").appendChild( fragment.cloneNode(true) );

正如我已经说过的,这是一个关于性能的问题,我知道作为一种最佳实践,建议使用片段,但我无法想到这样做只是创建一个新对象在内存中并没有做任何事情,所以我假设在这种情况下放弃片段是有效的。

希望有些js guru / god会在这里发出希望之光并帮助我们解决这个问题。


编辑:所以,我一直在寻找与此问题相关的内容,似乎documentFragments并不一定意味着更好的性能。

它只是节点的“内存”容器。一个片段和一个<div>之间的区别在于片段没有父节点,它永远不会在DOM上,只在内存中,这意味着片段上的操作更快,因为没有操作DOM。

W3C关于documentFragments的文档非常含糊,但至关重要的是,每个人最喜欢的浏览器都没有使用真正的片段,而是根据this MSDN documentation创建了一个新文档。这意味着,IE上的片段速度较慢。

所以,问题占上风,如果我在变量中创建一个元素(例如<div>但是不要将它附加到DOM中,添加元素(div,表格等)和东西,完成所有工作后(循环,验证,元素样式),该元素被追加,是不是与片段相同?

考虑到IE使用“假”片段的事实我至少在IE中使用这种方法(使用像div这样的元素,而不是片段)更好,我真的不关心IE但是我需要测试它(办公室的政策)。

另外,如果我像这样在数组上创建所有html:

var arrHTML = ["<table>","<tr>", ....]; 

然后执行此操作

document.getElementById("someElement").innerHTML = arrHTML.join(""); 

在IE上速度更快,但其他主流浏览器(FF,Chrome,Safari和Opera)在使用容器然后附加它(片段或div)时表现更好。

所有这一切都是因为创建所有元素的过程非常快,大约8-10秒创建多达20,000行24列,这是很多元素/标签,但浏览器似乎冻结了几秒钟,当他们一次全部附加,如果我试图逐个追加它们,那就太糟糕了。

再次感谢大家,这真的很有趣也很有趣。

7 个答案:

答案 0 :(得分:21)

当用于在多个位置中插入元素集时,

文档片段 快得多。这里的大多数答案指出它没有效用,但这是为了展示它的力量。

让我们举个例子。

假设我们需要在 10个元素中使用容器附加 20 div

没有

var elements = [];
for(var i=20; i--;) elements.push(document.createElement("div"));

var e = document.getElementsByClassName("container");
for(var i=e.length; i--;) {
  for(var j=20; j--;) e[i].appendChild(elements[j].cloneNode(true));
}


使用:

var frag = document.createDocumentFragment();
for(var i=20; i--;) frag.appendChild(document.createElement("div"));

var e = document.getElementsByClassName("container");
for(var i=e.length; i--;) e[i].appendChild(frag.cloneNode(true));

对于我来说,使用文档片段在Chrome 48上的速度提高了16倍。

Test on JsPerf

答案 1 :(得分:10)

通常你会想要使用片段来避免重排(重新绘制页面)。一个很好的例子是,如果你循环某些东西并在循环中追加,但是,我认为现代浏览器已经为此进行了优化。

我设置了一个jsPerf来说明何时使用片段here的一个很好的例子。你会注意到在Chrome中几乎没有区别(我认为现代优化在工作中),然而,在IE7中我得到的是没有片段的.08 ops / sec,带有片段的3.28 ops / sec。

因此,如果您循环遍历大型数据集并附加大量元素,请使用片段,这样您只需要一次重排。如果您只是几次附加到dom,或者您只是针对现代浏览器,则没有必要。

答案 2 :(得分:6)

我已经编写了一个jspref来测试它,看起来节点片段的速度提高了2.34%

http://jsperf.com/document-fragment-test-peluchetti

答案 3 :(得分:1)

根据我的经验,dom操作通常只在调用堆栈为空后才会发生。如果我在循环中放入大量的dom操作,浏览器只会冻结一段时间然后立即显示所有内容。如果需要,可以使用setTimeout中断堆栈以更频繁地显示结果。出于这个原因,我认为这两种方法应该表现相似。实际上这有点非常奇怪,因为如果在一个堆栈中你改变了一些元素,你将永远不会在改变之前看到它的状态(这个问题与进度通知对象是innerHTML在循环期间从未更新过 - 只是开始状态然后是最终状态)。

答案 4 :(得分:0)

我和OP有完全相同的问题,在阅读完所有答案和评论之后,似乎没有人真正理解OP的要求。

我从测试中得到了一个提示Nicola Peluchetti发布并修改了一下。

片段测试不是将元素附加到<div>然后附加到documentFragment,而是将元素直接附加到其上(documentFragment)而不是先到<div>。此外,为了避免任何隐藏的开销成本,两个测试都从创建<div>容器和documentFragment开始,而每个测试仅使用一个或另一个。

我认为最初的问题是,使用<div>documentFragment作为容器单个附加节点会更快吗?

使用<div>看起来更快,至少在Chrome 49上。

http://jsperf.com/document-fragment-test-peluchetti/39

documentFragment(目前)我能想到的唯一用例是它是否需要更少的内存(可以忽略不计),或者如果你有一堆兄弟节点要追加哪个你不喜欢我想放入一个“容器”元素。 documentFragment就像一个包装器,它只溶解它的内容。

答案 5 :(得分:0)

&#13;
&#13;
<!DOCTYPE html>
<html>
<head>
    <title>TODO supply a title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

</head>
<body>
<div ms-controller='for1'>
    <ul>

    </ul>
</div>

<script>
    var ul = document.querySelector('ul');
    console.time('no fragment');
    for(var i=0;i<1000000;i++){
        var li = document.createElement('li');
        li.innerText = i;

        ul.appendChild(li);
    }
    console.timeEnd('no fragment');

    console.time('has fragment');;
    var frg = document.createDocumentFragment()
    for(var i=0;i<1000000;i++){
        var li = document.createElement('li');
        li.innerText = i+'fragment';
        frg.appendChild(li);

    }
    ul.appendChild(frg)
    console.timeEnd('has fragment');
</script>
</body>
</html>
&#13;
&#13;
&#13;

结果是 没有片段:1615.278ms testFragment.html:36有片段:2908.286ms

所以,没有碎片更快。 我认为原因是铬做了一些事情。

答案 6 :(得分:0)

来自wolfram77的jsperf在非片段示例中包含一个额外的for循环,这是性能差异的主要原因,而不是DocumentFragment。通过删除这个额外的for循环,您可以获得相同的结果,但性能完全不同:

Example on jsperf.com

所以我仍然没有看到脚本部分的性能优势,但是当浏览器必须为每个附加元素重新绘制时,可能会有一个。