DocumentFragment真的能提高 JS 動態添加 DOM 的性能嗎?

不知從何時起在網上看到過一篇博文提到利用DocumentFragment提高JS動態添加大量DOM的性能,主要論據是每次調用node.appendChild()都會引起 reflow,而通過先把 DOM 暫存在DocumentFragment,從而達到只需要一次 reflow的目的。

當時還是太年輕的我從此便奉為金科玉律。直到今天不知何故突然腦洞大開:在瀏覽器當中 JS 不是會阻塞 UI 的渲染嗎,那為什麼在一個循環裡邊動態添加多個 DOM 會引起多次 reflow 呢?這明顯不科學嘛,此時 UI 應該是被 blocked 才對。。

於是我寫了一些代碼來測試我的想法。。

/* 常規方式 */
document.addEventListener("DOMContentLoaded", function() {
var date = new Date();
for (var i = 0; i &< 50000; i++) { var tmpNode = document.createElement("div"); tmpNode.innerHTML = "test" + i; document.body.appendChild(tmpNode); } console.log(new Date() - date); });

/* DocumentFragment方式 */
document.addEventListener("DOMContentLoaded", function() {
var date = new Date(), fragment = document.createDocumentFragment();
for (var i = 0; i &< 50000; i++) { var tmpNode = document.createElement("div"); tmpNode.innerHTML = "test" + i; fragment.appendChild(tmpNode); } document.body.appendChild(fragment); console.log(new Date() - date); });

測試結果發現時間基本無差別。。難道是現在的瀏覽器高端到 JS 不會阻塞 UI 渲染了嗎?於是我又寫了些代碼。。

&& &

比如當你append到document時,被append進去的元素的樣式表的計算是同步發生的,此時getComputedStyle可以得到樣式的計算值。而append到document fragment,沒樣式表什麼事,可以省下這個計算。再如,script節點只有append到document時,才會真的parse和執行。

注意阻塞UI不是阻塞reflow。如果你在append到document之後去訪問下clientHeight這樣的屬性,其實會block住直到reflow完成。但是如果你不訪問這類屬性,瀏覽器沒有必要在這個點進行強制reflow,而可以等待到腳本執行完。

現代瀏覽器的優化做得更好,所以類似的,如果你在append到document之後沒有訪問getComputedStyle之類的,瀏覽器也可以把樣式表計算推遲到腳本執行之後。所以你測下來 append 到 document fragment 和直接 append 到 document 上就差不多了。儘管通常情況下,性能現在沒有很大差異,但是作為靠譜程序員,你在追加dom時,用document fragement,是在代碼層面明確:這裡插入時,不需要(不應該)發生插入document的效果。所以該寫的地方還是要寫。


我在 Opera 26.0 和 IE 11 中測試的結果和 LZ 的類似,即目前的瀏覽器似乎不會在 JS 操作 DOM 的時候傻乎乎的去 reflow 或者 repaint 了。

題主的的測試結果,與 「JS 會阻塞 UI 渲染」 這個判斷不衝突啊。

按測試的結果來看,現代的瀏覽器,不論是否使用 DocumentFragment,或者直接操作 DOM ,都不會在 JS 運行期間 reflow 或者 repaint


其實還有一個更大的用處是壓制和校驗腳本的執行


早期,人們每次循環都把拼好的DIV append到瀏覽器DOM中一個container里,比如JS寫一個500次的循環,瀏覽器每次都執行重排,這樣重排了500次。

人們發現500次重排計算讓瀏覽器做實在太耗費性能了。

於是人們想,為什麼不把這500個DIV以DOM的方式在內存里事先做好,然後一次append到瀏覽器里,這樣瀏覽器一次處理的重排量雖然很大,但是比500次的小型重排的效率要快得多。


console.log是非同步的吧。。。


我記得在v8引擎優化之後 這些奇技淫巧往往會讓實際速度變慢


這是什麼時候的問題了?不過最近看到這個概念,做測試的時候,發現相對於時間來說已經沒什麼差別了。反而DocumentFragment這個玩意可能時間更長。因為需要創建新的佔位符,以及對佔位符操作。我想應該現在瀏覽器都做了節流的操作。


老哥,我怎麼覺得是你的代碼寫得有問題。。。

你現在做實驗想得出的結論,是「一條條地插元素到dom,和把元素先全都插到document fragment裡面然後再一次性地插到dom里,兩者沒有區別」

所以第二段記時的開始,應該是document.appendChild(frament)之前啊。。。

var date, fragment = document.createDocumentFragment();
for (var i = 0; i &< 50000; i++) { var tmpNode = document.createElement("div"); tmpNode.innerHTML = "test" + i; fragment.appendChild(tmpNode); } date = new Date(); document.body.appendChild(fragment); console.log(new Date() - date);

然而這樣也還是不夠嚴謹,因為你第一段代碼的意思是:「計算生成50000個div,往每個div里插內容,然後append每個div到document里的總時間」,也就是說,你得出的總時間不只是append的,還有create和innerHTML賦值的。

把兩個階段分開好吧


推薦閱讀:

如何優化vue的內存佔用?
能否用 JS 實現一個 CSS 解釋器?
如何理解 Facebook 的 flux 應用架構?
js中alert函數的實現原理是什麼?
請問js非同步和同步載入在性能優化中有什麼區別?

TAG:前端開發 | JavaScript | DOM |