2013-01-07 26 views
27

我對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); 

此代碼只是創建了一些其他功能的外殼來創建一個網格,建立網格的過程非常複雜,有許多驗證和目前我使用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 guru/god會在這裏發出一線希望,並幫助我們解決這個問題。


編輯:所以,我一直在四處尋找此問題相關的東西,它似乎documentFragments並不一定意味着更好的性能。

這只是一個「內存」容器的節點。一個片段和一個<div>之間的區別在於該片段沒有父對象,並且它永遠不會在DOM上,只是在內存中,這意味着由於沒有對DOM進行操作,所以對片段所做的操作更快。

W3C關於documentFragments的文檔非常含糊,不過在每個人最喜歡的瀏覽器中並沒有使用真正的片段,而是根據this MSDN documentation創建了一個新文檔。這意味着,IE上的片段速度較慢。

所以,問題普遍存在,如果我創建元素(例如<div>在可變但不是附加IT到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

將它放在jsperf上,看看目標受衆瀏覽器上哪個更快。 – jbabey

+0

我已經在firefox和chrome中使用了控制檯的配置文件,並且它們都在同一時間(大約180ms)與頭部和數據div中的實際數據執行,我測試了沒有碎片的「香草」方式,只是將所有內容附加到cointainer和那個容器將它附加到DOM,也使用片段並在變量上創建整個html,然後向該變量添加一個innerHTML,它們似乎都工作得差不多,但我的問題是性能明智的,這是關於記憶和類似的東西更好。謝謝 –

+0

根據我的理解,你會使用文檔片段來避免大量的重排,但如果你只是追加一小部分(比如你所展示的),那真的不值得。 – Snuffleupagus

回答

5

我寫了一個jspref來測試這一點,它出現的節點片段是快2.34%

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

+0

謝謝你,用firefox和safari測試它們都表明不使用碎片的速度要快得多,IE(8)只是死掉了。但問題仍然存在,使用Fragments實際上比僅向DOM添加一個元素(包含子元素,如示例)的性能更好? –

+0

@SamCcp在Firefox中,我的效果與Chrome中沒有區別。 –

+1

是的,如果你看看你的jsperf的瀏覽器,你會注意到最好的結果是沒有在所有測試了你的jsperf的瀏覽器中使用片段的結果,所以基於此,我可以說使用片段只是減慢速度,另一件要考慮的事情是,當循環太大時,在IE(至少8)中最好使用innerHTML,而不是將元素附加到dom,fragment或not中。 –

10

通常你想使用一個片段,以避免迴流(重繪頁)。一個好的例子是,如果你循環了一些東西並在循環中追加,但是,我認爲現代瀏覽器已經對此進行了優化。

我設置了一個jsPerf來說明何時使用片段here的一個很好的例子。你會注意到在Chrome中幾乎沒有什麼區別(我認爲在工作中是現代優化),但是,在IE7中,我得到了沒有片段的.08 ops/sec,帶有片段的3.28 ops/sec。

因此,如果你正在循環一個大型數據集並追加很多元素,請使用片段來代替,因此只有一個迴流。如果你只是追加了幾次,或者你只是瞄準現代瀏覽器,這是沒有必要的。

+0

是的,我通過一個不同大小的json對象循環,我正在做的是在內存中創建一個元素並在每個循環中添加東西,然後將該元素添加到頁面(DOM)中,所以它只是一個迴流而不是多個迴流,正如我所說的,該方法運行良好,速度非常快,無論是使用碎片還是在數組變量中創建html,然後像這樣添加它。elementHTML = array.join(「」);大多數瀏覽器顯示網格的速度非常快,最糟糕的情況是20,000行,持續10秒。我懷疑如果這個片段真的是一個改進。謝謝! –

+1

@SamCcp yerp,如果你沒有追加到循環內的DOM,一個片段不會加快速度。 – Snuffleupagus

+0

接下來會想到另外一件事,如果您在循環內將元素附加到DOM中,甚至連一個片段都不會幫助您,您會迫使瀏覽器正確迴流?那麼,這些碎片有什麼用呢?情節變厚了.... –

1

根據我的經驗,dom操作通常只在調用堆棧爲空時發生。如果我在循環中投入很多dom操作,瀏覽器會凍結一段時間,然後一次顯示所有內容。如果需要,可以使用setTimeout更頻繁地顯示結果來打破堆棧。出於這個原因,我相信這兩種方法應該有相似的表現。這實際上有時很奇怪,因爲如果在一個堆棧中更改某個元素,在更改之前您將永遠不會看到它的狀態(在進程通知對象中,innerHTML在循環期間從未更新過 - 只是開始狀態,然後是最終狀態)。

+0

這取決於什麼瀏覽器和什麼DOM元素。 – Pacerier

15

文檔片段快得多當它被用來插入組元素多個地方。這裏的大部分答案都指出了它的不實用性,但這是爲了展示它的實力。

讓我們舉個例子吧。

說,我們需要追加20的div10元帶班容器

沒有:

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)); 

對我來說,使用文檔片段原來是快16倍的Chrome 48

Test on JsPerf

+1

**這是最好的答案**雖然這裏的jsPerf測試沒有包含頁面重排(避免頁面重排是使用DocumentFragments的主要優點),但它的確說明了使用DocumentFragments避免DOM遍歷的真實性能。 DocumentFragment代碼的執行速度與Chrome 48中的非片段代碼一樣快,在IE11中快了近4倍。 如果您是開發人員構建使用JavaScript動態呈現的頁面,這就是您使用DocumentFragments的原因。 –

+1

謝謝!然而,要學習頁面重排,以及何時發生。 [This](https://dev.opera.com/articles/efficient-javascript/?page=3#reflow)似乎很詳細。 – wolfram77

+0

@ wolfram77你爲什麼要深入克隆這個片段?難道你只是appendChild(frag),適用於所有瀏覽器。 – AntonB

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就像是一個包裝,它會溶解只留下其內容。

0

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

所以,沒有片段是更快。 我認爲原因是鉻已經做了一些事情。

+0

你在'has fragment'中的字符串大小几乎翻了一番,通過在這個結果上做'+'fragment'' – swajak

0

來自wolfram77的jsperf在非片段示例中包含一個額外的for循環,這是導致性能差異的主要原因,而不是DocumentFragment。通過消除這種額外的循環,可以達到同樣的效果,但性能卻完全不同:

Example on jsperf.com

所以我還沒有看到在腳本部分的性能優勢,但有可能是一個在瀏覽器何時必須重新繪製每個附加元素。

+0

,NoFragment破壞了片段之一,我可以在瀏覽器上看到你重新繪製的點,但是如果你追加只有一個元素,只有一個重繪要完成。所以這是微不足道的恕我直言。 –