廢話少說,先來看個(gè)圖:
從上面這個(gè)圖中,我們可以看到那么幾個(gè)事:
1)瀏覽器會解析三個(gè)東西:
一個(gè)是HTML/SVG/XHTML,事實(shí)上,Webkit有三個(gè)C++的類對應(yīng)這三類文檔。解析這三種文件會產(chǎn)生一個(gè)DOM Tree。 CSS,解析CSS會產(chǎn)生CSS規(guī)則樹。 Javascript,腳本,主要是通過DOM API和CSSOM API來操作DOM Tree和CSS Rule Tree.
2)解析完成后,瀏覽器引擎會通過DOM Tree 和 CSS Rule Tree 來構(gòu)造 Rendering Tree。注意:
Rendering Tree 渲染樹并不等同于DOM樹,因?yàn)橐恍┫馠eader或display:none的東西就沒必要放在渲染樹中了。 CSS 的 Rule Tree主要是為了完成匹配并把CSS Rule附加上Rendering Tree上的每個(gè)Element。也就是DOM結(jié)點(diǎn)。也就是所謂的Frame。 然后,計(jì)算每個(gè)Frame(也就是每個(gè)Element)的位置,這又叫l(wèi)ayout和reflow過程。
3)最后通過調(diào)用操作系統(tǒng)Native GUI的API繪制。
DOM解析 HTML的DOM Tree解析如下:
Web page parsing Web page parsing
This is an example Web page.
上面這段HTML會解析成這樣:
CSS的解析大概是下面這個(gè)樣子(下面主要說的是Gecko也就是Firefox的玩法),假設(shè)我們有下面的HTML文檔:
A few quotes Franklin said that "A penny saved is a penny earned."FDR said "We have nothing to fear but fear itself."
于是DOM Tree是這個(gè)樣子:
然后我們的CSS文檔是這樣的:
/* rule 1 */ doc { display: block; text-indent: 1em; }/* rule 2 */ title { display: block; font-size: 3em; }/* rule 3 */ para { display: block; }/* rule 4 */ [class="emph"] { font-style: italic; }
于是我們的CSS Rule Tree會是這個(gè)樣子:
注意,圖中的第4條規(guī)則出現(xiàn)了兩次,一次是獨(dú)立的,一次是在規(guī)則3的子結(jié)點(diǎn)。所以,我們可以知道,建立CSS Rule Tree是需要比照著DOM Tree來的。CSS匹配DOM Tree主要是從右到左解析CSS的Selector,好多人以為這個(gè)事會比較快,其實(shí)并不一定。關(guān)鍵還看我們的CSS的Selector怎么寫了。
注意:CSS匹配HTML元素是一個(gè)相當(dāng)復(fù)雜和有性能問題的事情。所以,你就會在N多地方看到很多人都告訴你,DOM樹要小,CSS盡量用id和class,千萬不要過渡層疊下去,……
通過這兩個(gè)樹,我們可以得到一個(gè)叫Style Context Tree,也就是下面這樣(把CSS Rule結(jié)點(diǎn)Attach到DOM Tree上):
所以,F(xiàn)irefox基本上來說是通過CSS 解析 生成 CSS Rule Tree,然后,通過比對DOM生成Style Context Tree,然后Firefox通過把Style Context Tree和其Render Tree(Frame Tree)關(guān)聯(lián)上,就完成了。注意:Render Tree會把一些不可見的結(jié)點(diǎn)去除掉。而Firefox中所謂的Frame就是一個(gè)DOM結(jié)點(diǎn),不要被其名字所迷惑了。
注:Webkit不像Firefox要用兩個(gè)樹來干這個(gè),Webkit也有Style對象,它直接把這個(gè)Style對象存在了相應(yīng)的DOM結(jié)點(diǎn)上了。
渲染的流程基本上如下(黃色的四個(gè)步驟):
注意:上圖流程中有很多連接線,這表示了Javascript動態(tài)修改了DOM屬性或是CSS屬會導(dǎo)致重新Layout,有些改變不會,就是那些指到天上的箭頭,比如,修改后的CSS rule沒有被匹配到,等。
這里重要要說兩個(gè)概念,一個(gè)是Reflow,另一個(gè)是Repaint。這兩個(gè)不是一回事。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每個(gè)結(jié)點(diǎn)都會有reflow方法,一個(gè)結(jié)點(diǎn)的reflow很有可能導(dǎo)致子結(jié)點(diǎn),甚至父點(diǎn)以及同級結(jié)點(diǎn)的reflow。在一些高性能的電腦上也許還沒什么,但是如果reflow發(fā)生在手機(jī)上,那么這個(gè)過程是非常痛苦和耗電的。 所以,下面這些動作有很大可能會是成本比較高的。
多說兩句關(guān)于滾屏的事,通常來說,如果在滾屏的時(shí)候,我們的頁面上的所有的像素都會跟著滾動,那么性能上沒什么問題,因?yàn)槲覀兊娘@卡對于這種把全屏像素往上往下移的算法是很快。但是如果你有一個(gè)fixed的背景圖,或是有些Element不跟著滾動,有些Elment是動畫,那么這個(gè)滾動的動作對于瀏覽器來說會是相當(dāng)相當(dāng)痛苦的一個(gè)過程。你可以看到很多這樣的網(wǎng)頁在滾動的時(shí)候性能有多差。因?yàn)闈L屏也有可能會造成reflow。
基本上來說,reflow有如下的幾個(gè)原因:
好了,我們來看一個(gè)示例吧:
var bstyle = document.body.style; // cachebstyle.padding = "20px"; // reflow, repaintbstyle.border = "10px solid red"; // 再一次的 reflow 和 repaintbstyle.color = "blue"; // repaintbstyle.backgroundColor = "#fad"; // repaintbstyle.fontSize = "2em"; // reflow, repaint// new DOM element - reflow, repaintdocument.body.appendChild(document.createTextNode('dude!'));
當(dāng)然,我們的瀏覽器是聰明的,它不會像上面那樣,你每改一次樣式,它就reflow或repaint一次。一般來說,瀏覽器會把這樣的操作積攢一批,然后做一次reflow,這又叫異步reflow或增量異步reflow。但是有些情況瀏覽器是不會這么做的,比如:resize窗口,改變了頁面默認(rèn)的字體,等。對于這些操作,瀏覽器會馬上進(jìn)行reflow。
但是有些時(shí)候,我們的腳本會阻止瀏覽器這么干,比如:如果我們請求下面的一些DOM值:
offsetTop, offsetLeft, offsetWidth, offsetHeightscrollTop/Left/Width/HeightclientTop/Left/Width/HeightIE中的 getComputedStyle(), 或 currentStyle
因?yàn)椋绻覀兊某绦蛐枰@些值,那么瀏覽器需要返回最新的值,而這樣一樣會flush出去一些樣式的改變,從而造成頻繁的reflow/repaint。
下面是一些Best Practices:
1)不要一條一條地修改DOM的樣式。與其這樣,還不如預(yù)先定義好css的class,然后修改DOM的className。
// badvar left = 10,top = 10;el.style.left = left + "px";el.style.top = top + "px";// Goodel.className += " theclassname";// Goodel.style.cssText += "; left: " + left + "px; top: " + top + "px;";
2)把DOM離線后修改。如:
使用documentFragment 對象在內(nèi)存里操作DOM 先把DOM給display:none(有一次reflow),然后你想怎么改就怎么改。比如修改100次,然后再把他顯示出來。 clone一個(gè)DOM結(jié)點(diǎn)到內(nèi)存里,然后想怎么改就怎么改,改完后,和在線的那個(gè)的交換一下。
3)不要把DOM結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。不然這會導(dǎo)致大量地讀寫這個(gè)結(jié)點(diǎn)的屬性。
4)盡可能的修改層級比較低的DOM。當(dāng)然,改變層級比較底的DOM有可能會造成大面積的reflow,但是也可能影響范圍很小。
5)為動畫的HTML元件使用fixed或absoult的position,那么修改他們的CSS是不會reflow的。
6)千萬不要使用table布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動會造成整個(gè)table的重新布局。
有時(shí)候,你會也許會發(fā)現(xiàn)在IE下,你不知道你修改了什么東西,結(jié)果CPU一下子就上去了到100%,然后過了好幾秒鐘repaint/reflow才完成,這種事情以IE的年代時(shí)經(jīng)常發(fā)生。所以,我們需要一些工具幫我們看看我們的代碼里有沒有什么不合適的東西。
Chrome下,Google的SpeedTracer是個(gè)非常強(qiáng)悍的工作讓你看看你的瀏覽渲染的成本有多大。其實(shí)Safari和Chrome都可以使用開發(fā)者工具里的一個(gè)Timeline的東東。 Firefox下這個(gè)基于Firebug的叫Firebug Paint Events的插件也不錯(cuò)。 IE下你可以用一個(gè)叫dynaTrace的IE擴(kuò)展。 最后,別忘了下面這幾篇提高瀏覽器性能的文章:
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com