【问题标题】:Does using a document fragment really improve performance?使用文档片段真的可以提高性能吗?
【发布时间】:2017-12-12 07:15:09
【问题描述】:

我对 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一个数组变量,另一个创建元素并将它们附加到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 大师/大神能在这里点亮希望之光,帮助我们解决这个问题。


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

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

W3C关于documentFragments 的文档非常模糊,但重点是,大家最喜欢的浏览器并没有使用真正的片段,而是根据this MSDN documentation 创建一个新文档。这意味着 IE 上的 Fragment 速度较慢。

所以,问题是,如果我创建一个元素(例如 <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 秒来创建多达 24 列的 20,000 行,这是很多元素/标签,但浏览器似乎冻结了几秒钟后它们都被一次附加,如果我尝试一个接一个地附加它们,那就太糟糕了。

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

【问题讨论】:

  • 粘贴在 jsperf 上,看看在目标受众的浏览器上哪个更快。
  • 我在 firefox 和 chrome 中使用了控制台的配置文件,它们都在同一时间(大约 180 毫秒)执行,标题和数据 div 中有真实数据,我已经测试了“香草”方式,没有片段,只是将所有内容附加到一个硬币容器,然后该容器将其附加到 DOM,还使用片段并在变量上创建整个 html,然后向该变量添加一个 innerHTML,它们似乎都或多或少地工作相同,但我的问题是性能方面的,这对于内存和类似的东西来说更好。谢谢
  • 据我了解,您会使用文档片段来避免大量回流,但如果您只是附加少量(例如您所显示的),那真的不值得它。
  • 要追加的元素越多,性能差异就越大。
  • @jbabey jsPerf 测试结果可能会产生误导。很难构建一个真正代表真实世界 DocumentFragment 使用的 jsPerf 测试。使用 DocumentFragments 的主要好处是可以避免昂贵的页面重排和/或 DOM 遍历。此处的 jsPerf 测试都没有展示避免页面重排带来的性能提升,只有 wolfram77 给出的答案展示了通过使用 DocumentFragments 避免大量 DOM 遍历所节省的成本。

标签: javascript performance documentfragment


【解决方案1】:

文档片段多个位置插入元素集快得多。这里的大多数答案都指出它没有用处,但这是为了展示它的力量。

举个例子。

假设我们需要在 10 个元素 中添加 20 个 divcontainer

没有:

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

【讨论】:

  • 这是最好的答案。 虽然这里的 jsPerf 测试不包含页面重排(避免页面重排是使用 DocumentFragments 的主要好处),但它确实说明了真正的性能使用 DocumentFragments 来避免 DOM 遍历的好处。 DocumentFragment 代码在 Chrome 48 中的执行速度是非片段代码的 2 倍,在 IE11 中几乎是 4 倍。如果您是使用 JavaScript 构建动态呈现页面的开发人员,这就是您使用 DocumentFragments 的原因。
  • 谢谢!尚未了解页面重排以及何时发生。 This 好像很详细。
  • @wolfram77 为什么要深度克隆片段?你不能只是 appendChild(frag),适用于所有浏览器。
  • @AntonB 我尝试了您的建议,但似乎文档片段的行为方式与普通元素相同,即它们从source 并被添加到 destination。然而,与元素不同的是,文档片段内容被添加,而不是整个片段,就像在普通元素的情况下发生的那样,因此可以一次添加多个元素。
  • @AntonB 仅当您打算仅使用片段一次时才可以使用appendChild(frag)
【解决方案2】:

通常您希望使用片段来避免重排(重新绘制页面)。一个很好的情况是,如果您在循环中循环某些内容并在循环中追加内容,但是,我认为现代浏览器已经对此进行了优化。

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

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

【讨论】:

  • 是的,我正在循环一个大小不同的 json 对象,我正在做的是在内存中创建一个元素并在每个循环上附加内容,然后将该元素添加到页面中(DOM),所以它只是一个重排而不是多个重排,正如我所说,该方法运行正常并且非常快,既使用片段又在数组变量中创建html然后像这样添加它 element.innerHTML = array.join ("");大多数浏览器显示网格的速度非常快,最糟糕的情况是 20,000 行,并且持续了 10 秒。我怀疑该片段是否真的是一种改进。谢谢!
  • @SamCcp yerp,如果你没有在循环中附加到 DOM,片段就不会加速它。
  • 然后想到另一件事,如果您在循环中将元素附加到 DOM,甚至片段都不会帮助您,您是在强制浏览器进行重排,对吗?那么,碎片又有什么用呢?情节变厚了……
  • @SamCcp 您可以继续附加到片段,然后将片段附加到循环之外的 DOM。
  • @SamCcp 事实上,只要它实际上没有被附加到 DOM 上,它并没有太大的区别。我猜一个片段会稍微快一点,创建一个 DIV 有一个更长的原型链,所以我想它会花费更长的时间;但那将是在几个 MS 的范围内,不值得优化。 (DocumentFragment 直接在Node 下,一个html 元素从HTMLSpanElement(或任何相关标签)到HTMLElementElementNode
【解决方案3】:

我写了一个jspref来测试它,节点片段似乎快了2.34%

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

【讨论】:

  • 谢谢你,用 firefox 和 safari 测试它都表明不使用 Fragments 要快得多,IE ( 8 ) 就死了。但是问题仍然存在,使用 Fragments 确实比仅将一个元素(其中包含子元素,如示例)附加到 DOM 性能更好?
  • @SamCcp 在 Firefox 中我的结果与 chrome 中的结果相同,但没有区别。
  • 是的,如果您从您的 jsperf 中查看 browserscope,您会注意到最好的结果是在所有测试过您的 jsperf 的浏览器中不使用片段的结果,因此基于此,我可以说使用片段只会减慢速度,另一件要考虑的事情是,当循环太大时,在 IE(至少 8 版)中使用 innerHTML 比将元素附加到 dom、片段或不更好。
  • 对于多达 1000 个节点,无论如何都没有明显的性能提升,甚至在毫秒内也没有,它只是增加了重写逻辑的编程复杂性。
  • @SamCcp 这个 jsPerf 测试有缺陷。 DocumentFragments 的重点是避免昂贵的页面 reflows 和/或 DOM 遍历。这个 jsPerf 测试不包含任何内容。 jsPerf 测试执行的 DOM 操作不可见,也没有页面重排。
【解决方案4】:

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

【讨论】:

  • 这取决于什么浏览器和什么DOM元素。
【解决方案5】:

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

我从 Nicola Peluchetti 发布的测试中得到启发,并对其进行了一些修改。

片段测试不是将元素附加到&lt;div&gt;then 附加到documentFragment,而是将元素直接附加到它(documentFragment)而不是先附加到@ 987654325@。此外,为了避免任何隐藏的开销成本,两个测试都从创建 &lt;div&gt; 容器和 documentFragment 开始,而每个测试只使用其中一个。

我认为最初的问题是,基本上,使用&lt;div&gt;documentFragment 作为容器单个追加节点是否更快?

看起来使用&lt;div&gt; 更快,至少在 Chrome 49 上。

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

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

【讨论】:

    【解决方案6】:

    <!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>

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

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

    【讨论】:

    • 通过 +'fragment' ?,您几乎将“有片段”中的字符串大小增加了一倍
    • 结果是对的。但不要加+'fragment'。这叫做控制变量
    【解决方案7】:

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

    Example on jsperf.com

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

    【讨论】:

    • 在这个结果上,NoFragment 破坏了片段之一,我可以在浏览器上的重绘上看到你的观点,但是如果你只附加一个元素,那么只有一个重绘要做。所以这可以忽略不计恕我直言。
    • 链接失效了。
    • 等等,wolfram77 显示片段确实不需要额外的循环。这显示了一个使用片段更快的用例。片段不需要循环,因为使用cloneNode(true) 会克隆所有子项,因此不需要循环。
    猜你喜欢
    • 2014-05-19
    • 1970-01-01
    • 1970-01-01
    • 2014-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    相关资源
    最近更新 更多