首先,你應該瞭解的就是,瀏覽器是如何渲染一個頁面的。javascript
先看一個大體的流程圖css
它的整體流程是這樣的:html
1)瀏覽器解析這三個東西:java
2)解析完成後,瀏覽器會根據DOM樹和CSS Rule樹來構造渲染樹(Rendering Tree)。瀏覽器
3)最後經過調用操做系統Native GUI的API繪製(painting)。緩存
拋去其中的細節,再簡單一點的說法就是:DOM樹解析->css解析->渲染(也就是構建渲染樹以及最終呈現到瀏覽器上的過程)性能優化
這裏 主要針對第三步的渲染過程進行一下講解:app
須要注意的是:Javascript若是動態修改了DOM屬性或是CSS屬會致使從新Layout(Reflow),固然有些屬性改變不會。異步
這裏,這裏重要要說兩個概念,一個是Reflow,另外一個是Repaint佈局
Repaint(重繪)
當在頁面上修改了一些不須要改變定位的樣式的時候(好比background-color
,border-color
,visibility
),瀏覽器只會將新的樣式從新繪製給元素(這就叫一次「重繪」或者「從新定 義樣式」)。這時只須要屏幕的一部分要重畫。
Reflow(重排)
當頁面上的改變影響了文檔內容、結構或者元素定位時,就會發生重排(或稱「從新佈局」)。重排一般由如下改變觸發:
這時,咱們須要從新驗證並計算Render Tree。是Render Tree的一部分或所有發生了變化。這就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式 佈局,因此,若是某元件的幾何尺寸發生了變化,須要從新佈局,也就叫reflow)reflow 會從<html>這個root frame開始遞歸往下,依次計算全部的結點幾何尺寸和位置,在 reflow過程當中,可能會增長一些frame,好比一個文本字符串必需被包裝起來。
能夠看出,這兩個動做對於瀏覽器的性能都有較大的影響,固然reflow的成本比repaint的成本高好多。那麼,瀏覽器又是如何避免成本增長,從而優化渲染的呢?
瀏覽器如何優化渲染?
一、瀏覽器盡最大努力限制重排
的過程僅覆蓋已更改的元素的區域。舉個例子,一個 position 爲 absolue 或 fixed 的元素的大小變化隻影響它自身和子孫元素,而對一個 position 爲 static 的元素作一樣的操做就會引發全部它後面元素的重排。
二、當運行一段Jjavascript 代碼的時候,瀏覽器會將一些修改緩存起來,而後當代碼執行的時候,一次性的將這些修改執行。舉例來講,這段代碼會觸發一次重繪和一次重排:
var bstyle = document.body.style; // cache bstyle.padding = "20px"; // reflow, repaint bstyle.border = "10px solid red"; // 再一次的 reflow 和 repaint bstyle.color = "blue"; // repaint bstyle.backgroundColor = "#fad"; // repaint bstyle.fontSize = "2em"; // reflow, repaint // new DOM element - reflow, repaint document.body.appendChild(document.createTextNode('dude!'));
瀏覽器不會像上面那樣,你每改一次樣式,它就reflow或repaint一次。通常來講,瀏覽器會把這樣的(都是設置style屬性,而不涉及其餘相似讀取屬性的操做)操做積攢一批,而後作一次reflow,這又叫異步reflow或增量異步reflow。可是有些狀況瀏覽器是不會這麼作的,好比:resize窗口,改變了頁面默認的字體,等。對於這些操做,瀏覽器會立刻進行reflow。
可是有些時候,咱們的腳本會阻止瀏覽器這麼幹,好比:若是咱們請求下面的一些DOM值:(好比咱們在上面的例子中若加一個讀取屬性的操做則會引發又一次的重排)
由於,若是咱們的程序須要這些值,那麼瀏覽器須要返回最新的值,而這樣同樣會flush出去一些樣式的改變,從而形成頻繁的reflow/repaint。
固然,咱們能夠經過改變書寫習慣而作一些認爲的性能優化:
實際優化建議
例如:
一、使用documentFragment 對象在內存裏操做DOM,相似如下的代碼示例:
// Create the fragment var fragment = document.createDocumentFragment(); //add DOM to fragment for(var i = 0; i < 10; i++) { var spanNode = document.createElement("span"); spanNode.innerHTML = "number:" + i; fragment.appendChild(spanNode); } //add this DOM to body document.body.appendChild(spanNode);
二、先把DOM給display:none(有一次reflow),而後你想怎麼改就怎麼改。好比修改100次,而後再把他顯示出來。
三、clone一個DOM結點到內存裏,而後想怎麼改就怎麼改,改完後,和在線的那個的交換一下
:hover
動畫是一個很好的主意(例如,給 body 標籤加一個 no-hover 的 class