為什么dom操作會影響性能?
在瀏覽器當中,dom的實現和ECMAScript的實現是分離的。
例如,在IE中,ECMAScrit的實現在jscript.dll中,而DOM的實現在mshtml.dll中;在Chrome中使用WebKit中的 WebCore處理DOM和渲染,但ECMAScript是在V8引擎中實現的,其他瀏覽器的情況類似。
因此,操作dom,就是通過js代碼調用dom的接口,就相當於兩個相互獨立的模塊發生了交互。這樣,相比於在同一個模塊當中互相調用,這種跨模塊的調用它的性能損耗是非常高的。
然而,dom操作影響性能最主要是因為它導致了瀏覽器的重繪(repaint)和重排(reflow)。
瀏覽器渲染原理
為了可以更加深刻地理解重繪和重排對性能的影響,需要簡單了解一下瀏覽器的渲染原理。
從下載文檔到渲染頁面的過程中,瀏覽器會通過解析HTML文檔來構建DOM樹,解析CSS產生CSS規則樹。JavaScript代碼在解析過程中, 可能會修改生成的DOM樹和CSS規則樹。之后根據DOM樹和CSS規則樹構建渲染樹,在這個過程中CSS會根據選擇器匹配HTML元素。渲染樹包括了每 個元素的大小、邊距等樣式屬性,渲染樹中不包含隱藏元素及head元素等不可見元素。最后瀏覽器根據元素的坐標和大小來計算每個元素的位置,並繪制這些元 素到頁面上。無論何時總會有一個初始化的頁面布局伴隨着一次繪制。
重繪
重繪,就是指頁面某些部分需要重新繪制,由於節點的幾何屬性發生改變或者由於樣式發生改變,例如改變元素背景色時,屏幕上的部分內容需要更新,而元素的位置和尺寸並沒有改變。
重排
元素的位置或尺寸發生了改變,瀏覽器需 要重新計算渲染樹,導致渲染樹的一部分或全部發生變化。渲染樹重新建立后,瀏覽器會重新繪制頁面上受影響的元素。
也就是說,重排,改變的是dom文檔的結構,例如dom元素的位置或者尺寸發生了變化,需要重新布局,就會發生重排。
優化方法:
1、將dom操作積累起來作批量操作
現代瀏覽器中會有優化方法,就是把dom操作積累起來,做批量處理。但是在有些情況下,瀏覽器會立即重排或重繪。比如請求如下的DOM元素布局信息:offsetTop/Left/Width/Height、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()或 currentStyle。因為這些值都是動態計算的,所以瀏覽器需要盡快完成頁面的繪制,然后計算返回值,從而打亂了重排或重繪的優化。
2、合並多次的DOM操作為單次的DOM操作
最常見頻繁進行DOM操作的是頻繁修改DOM元素的樣式,代碼類似如下:
element.style.borderColor = '#f00'; element.style.borderStyle = 'solid'; element.style.borderWidth = '1px';
這種編碼方式會因為頻繁更改DOM元素的樣式,觸發頁面多次的重排或重繪,上面介紹過,現代瀏覽器針對這種情況有性能的優化,它會合並DOM操作,但並不是所有的瀏覽器都存在這樣的優化。推薦的方式是把DOM操作盡量合並,如上的代碼可以優化為:
// 優化方案1 element.style.cssText += 'border: 1px solid #f00;'; // 優化方案2 element.className += 'empty';
示例的代碼有兩種優化的方案,都做到了把多次的樣式設置合並為一次設置。方案2比方案1稍微有一些性能上的損耗,因為它需要查詢CSS類。但方案2的維護性最好,這在上一章曾經討論過。很多時候,如果性能問題並不突出,選擇編碼方案時需要優先考慮的是代碼的維護性。
類似的操作還有通過innerHTML接口修改DOM元素的內容。不要直接通過此接口來拼接HTML代碼,而是以字符串方式拼接好代碼后,一次性賦值給DOM元素的innerHTML接口。
3、使用文檔片段
文檔片段是一個輕量級的document對象,並不會和特定的頁面關聯。通過在文檔片段上進行DOM操作,可以降低DOM操作對頁面性能的影響,這 種方式是創建一個文檔片段,並在此片段上進行必要的DOM操作,操作完成后將它附加在頁面中。對頁面性能的影響只存在於最后把文檔片段附加到頁面的這一步 操作上。代碼類似如下:
var fragment = document.createDocumentFragment(); // 一些基於fragment的大量DOM操作 ... document.getElementById('myElement').appendChild(fragment);
4、通過設置DOM元素的display樣式為none來隱藏元素
這種方式是通過隱藏頁面的DOM元素,達到在頁面中移除元素的效果,經過大量的DOM操作后恢復元素原來的display樣式。對於這類會引起頁面重繪或重排的操作,就只有隱藏和顯示DOM元素這兩個步驟了。代碼類似如下:
var myElement = document.getElementById('myElement'); myElement.style.display = 'none'; // 一些基於myElement的大量DOM操作 ... myElement.style.display = 'block';
5、克隆DOM元素到內存中
這種方式是把頁面上的DOM元素克隆一份到內存中,然后再在內存中操作克隆的元素,操作完成后使用此克隆元素替換頁面中原來的DOM元素。這樣一來,影響性能的操作就只是最后替換元素的這一步操作了,在內存中操作克隆元素不會引起頁面上的性能損耗。代碼類似如下:
var old = document.getElementById('myElement'); var clone = old.cloneNode(true); // 一些基於clone的大量DOM操作 ... old.parentNode.replaceChild(clone, old);
6、設置具有動畫效果的DOM元素的position屬性為fixed或absolute
把頁面中具有動畫效果的元素設置為絕對定位,使得元素脫離頁面布局流,從而避免了頁面頻繁的重排,只涉及動畫元素自身的重排了。這種做法可以提高動 畫效果的展示性能。如果把動畫元素設置為絕對定位並不符合設計的要求,則可以在動畫開始時將其設置為絕對定位,等動畫結束后恢復原始的定位設置。在很多的 網站中,頁面的頂部會有大幅的廣告展示,一般會動畫展開和折疊顯示。如果不做性能的優化,這個效果的性能損耗是很明顯的。使用這里提到的優化方案,則可以 提高性能。