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); });測試結果發現時間基本無差別。。難道是現在的瀏覽器高端到 JS 不會阻塞 UI 渲染了嗎?於是我又寫了些代碼。。
/* 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 的,於是我迷茫了。。求大神答疑解惑~~
題主你怎麼從寫的第二段代碼得出「JS 不會阻塞 UI 渲染了」的看法的?
有些事情只在節點被插入到真正的document時才發生,這是document fragment更快的基本原因。document fragment從機制上比較接近innerHTML,只是前者確保了dom結構。其他大量的事情其實要等到被插入document之後才發生。
比如當你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 |