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

IT技术 javascript 表现 文件片段
2021-03-21 22:19:16

我对 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 来创建网格,创建网格的过程非常复杂并且有很多验证,目前我使用 2 种方法来填充网格,一种在数组变量中创建整个 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 并不一定意味着更好的性能。

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

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

所以,问题是,如果我在变量中创建一个元素<div>例如 a)但不要将它附加到 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 列,这是很多元素/标签,但浏览器似乎冻结了几秒钟它们都是一次附加的,如果我尝试将它们一个一个地附加,那就太糟糕了。

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

6个回答

文档片段更快,当它被用来插入一组元素多个地方这里的大多数答案都指出它没有用,但这是为了证明它的实力。

让我们举个例子。

假设我们需要container 的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 倍。

在 JsPerf 上测试

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

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

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

因此,如果您要循环处理大型数据集并附加很多元素,请改用片段,这样您就只有一次重排。如果您只附加到 dom 几次,或者您只针对现代浏览器,则没有必要。

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

我写了一个 jspref 来测试这个,看起来节点片段快了 2.34%

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

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

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

这取决于什么浏览器和什么 DOM 元素。
2021-05-17 22:19:16

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

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

代替追加的元件到的<div>然后附加到documentFragment,所述片段的测试得到的元件直接附加到它(documentFragment),而不是第一至<div>此外,为了避免任何隐藏的开销成本,两个测试都首先创建<div>容器和documentFragment,而每个测试只使用一个或另一个。

我认为最初的问题是,基本上,使用 a或 a作为容器执行单个节点附加是否更快<div>documentFragment

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

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

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