看到這個(gè)標(biāo)題大家一定會(huì)想到這篇神文《How Browsers Work》算行,這篇文章把瀏覽器的很多細(xì)節(jié)講得很細(xì)里伯,而且也被翻譯成了中文蔗衡。為什么我還想寫(xiě)一篇呢再榄?因?yàn)閮蓚€(gè)原因
1) 這篇文章太長(zhǎng)了苗胀,閱讀成本太大襟诸,不能一口氣讀完。
2) 花了大力氣讀了這篇文章后可以了解很多基协,但似乎對(duì)工作沒(méi)什么幫助歌亲。
所以,我準(zhǔn)備寫(xiě)下這篇文章來(lái)解決上述兩個(gè)問(wèn)題澜驮。希望你能在上班途中陷揪,或是坐馬桶時(shí)就能讀完,并能從中學(xué)會(huì)一些能用在工作上的東西。
瀏覽器工作大流程
廢話(huà)少說(shuō)悍缠,先來(lái)看個(gè)圖:
從上面這個(gè)圖中卦绣,我們可以看到那么幾個(gè)事:
1) 瀏覽器會(huì)解析三個(gè)東西:
- 一個(gè)是HTML/SVG/XHTML,事實(shí)上扮休,Webkit有三個(gè)C++的類(lèi)對(duì)應(yīng)這三類(lèi)文檔迎卤。解析這三種文件會(huì)產(chǎn)生一個(gè)DOM Tree。
- CSS玷坠,解析CSS會(huì)產(chǎn)生CSS規(guī)則樹(shù)蜗搔。
- Javascript,腳本八堡,主要是通過(guò)DOM API和CSSOM API來(lái)操作DOM Tree和CSS Rule Tree.
2) 解析完成后樟凄,瀏覽器引擎會(huì)通過(guò)DOM Tree 和 CSS Rule Tree 來(lái)構(gòu)造 Rendering Tree。注意:
- Rendering Tree 渲染樹(shù)并不等同于DOM樹(shù)兄渺,因?yàn)橐恍┫馠eader或display:none的東西就沒(méi)必要放在渲染樹(shù)中了缝龄。
- CSS 的 Rule Tree主要是為了完成匹配并把CSS Rule附加上Rendering Tree上的每個(gè)Element。也就是DOM結(jié)點(diǎn)挂谍。也就是所謂的Frame叔壤。
- 然后,計(jì)算每個(gè)Frame(也就是每個(gè)Element)的位置口叙,這又叫l(wèi)ayout和reflow過(guò)程炼绘。
3)最后通過(guò)調(diào)用操作系統(tǒng)Native GUI的API繪制。
DOM解析
HTML的DOM Tree解析如下:
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>
上面這段HTML會(huì)解析成這樣:
下面是另一個(gè)有SVG標(biāo)簽的情況妄田。
CSS解析
CSS的解析大概是下面這個(gè)樣子(下面主要說(shuō)的是Gecko也就是Firefox的玩法)俺亮,假設(shè)我們有下面的HTML文檔:
<doc>
<title>A few quotes</title>
<para>
Franklin said that <quote>"A penny saved is a penny earned."</quote>
</para>
<para>
FDR said <quote>"We have nothing to fear but <span>fear itself.</span>"</quote>
</para>
</doc>
于是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會(huì)是這個(gè)樣子:
注意,圖中的第4條規(guī)則出現(xiàn)了兩次疟呐,一次是獨(dú)立的脚曾,一次是在規(guī)則3的子結(jié)點(diǎn)。所以启具,我們可以知道本讥,建立CSS Rule Tree是需要比照著DOM Tree來(lái)的。CSS匹配DOM Tree主要是從右到左解析CSS的Selector富纸,好多人以為這個(gè)事會(huì)比較快囤踩,其實(shí)并不一定。關(guān)鍵還看我們的CSS的Selector怎么寫(xiě)了晓褪。
注意:CSS匹配HTML元素是一個(gè)相當(dāng)復(fù)雜和有性能問(wèn)題的事情堵漱。所以,你就會(huì)在N多地方看到很多人都告訴你涣仿,DOM樹(shù)要小勤庐,CSS盡量用id和class示惊,千萬(wàn)不要過(guò)渡層疊下去,……
通過(guò)這兩個(gè)樹(shù)愉镰,我們可以得到一個(gè)叫Style Context Tree米罚,也就是下面這樣(把CSS Rule結(jié)點(diǎn)Attach到DOM Tree上):
所以,F(xiàn)irefox基本上來(lái)說(shuō)是通過(guò)CSS 解析 生成 CSS Rule Tree丈探。然后录择,通過(guò)比對(duì)DOM生成Style Context Tree,然后Firefox通過(guò)把Style Context Tree和其Render Tree(Frame Tree)關(guān)聯(lián)上碗降,就完成了隘竭。
注意:Render Tree會(huì)把一些不可見(jiàn)的結(jié)點(diǎn)去除掉。而Firefox中所謂的Frame就是一個(gè)DOM結(jié)點(diǎn)讼渊,不要被其名字所迷惑了动看。
注:Webkit不像Firefox要用兩個(gè)樹(shù)來(lái)干這個(gè),Webkit也有Style對(duì)象爪幻,它直接把這個(gè)Style對(duì)象存在了相應(yīng)的DOM結(jié)點(diǎn)上了菱皆。
渲染
渲染的流程基本上如下(黃色的四個(gè)步驟):
<b>
1.計(jì)算CSS樣式
2.構(gòu)建Render Tree
3.Layout – 定位坐標(biāo)和大小,是否換行挨稿,各種position, overflow, z-index屬性 ……
正式開(kāi)畫(huà)
4.正式開(kāi)畫(huà)
</b>
注意:上圖流程中有很多連接線(xiàn)仇轻,這表示了Javascript動(dòng)態(tài)修改了DOM屬性或是CSS屬會(huì)導(dǎo)致重新Layout,有些改變不會(huì)奶甘,就是那些指到天上的箭頭拯田,比如,修改后的CSS rule沒(méi)有被匹配到甩十,等。
這里重要要說(shuō)兩個(gè)概念吭产,一個(gè)是Reflow侣监,另一個(gè)是Repaint。這兩個(gè)不是一回事臣淤。
Repaint——屏幕的一部分要重畫(huà)橄霉,比如某個(gè)CSS的背景色變了。但是元素的幾何尺寸沒(méi)有變邑蒋。
Reflow——意味著元件的幾何尺寸變了姓蜂,我們需要重新驗(yàn)證并計(jì)算Render Tree。是Render Tree的一部分或全部發(fā)生了變化医吊。這就是Reflow钱慢,或是Layout。(HTML使用的是flow based layout卿堂,也就是流式布局束莫,所以懒棉,如果某元件的幾何尺寸發(fā)生了變化,需要重新布局览绿,也就叫reflow)reflow 會(huì)從<html>這個(gè)root frame開(kāi)始遞歸往下策严,依次計(jì)算所有的結(jié)點(diǎn)幾何尺寸和位置,在reflow過(guò)程中饿敲,可能會(huì)增加一些frame妻导,比如一個(gè)文本字符串必需被包裝起來(lái)。
下面是一個(gè)打開(kāi)Wikipedia時(shí)的Layout/reflow的視頻(注:HTML在初始化的時(shí)候也會(huì)做一次reflow怀各,叫intial reflow)倔韭,你可以感受一下:
http://v.youku.com/v_show/id_XMzI5MDg0OTA0.html
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每個(gè)結(jié)點(diǎn)都會(huì)有reflow方法渠啤,一個(gè)結(jié)點(diǎn)的reflow很有可能導(dǎo)致子結(jié)點(diǎn)狐肢,甚至父點(diǎn)以及同級(jí)結(jié)點(diǎn)的reflow。在一些高性能的電腦上也許還沒(méi)什么沥曹,但是如果reflow發(fā)生在手機(jī)上份名,那么這個(gè)過(guò)程是非常痛苦和耗電的。
所以妓美,下面這些動(dòng)作有很大可能會(huì)是成本比較高的僵腺。
- 當(dāng)你增加、刪除壶栋、修改DOM結(jié)點(diǎn)時(shí)辰如,會(huì)導(dǎo)致Reflow或Repaint
- 當(dāng)你移動(dòng)DOM的位置,或是搞個(gè)動(dòng)畫(huà)的時(shí)候贵试。
- 當(dāng)你修改CSS樣式的時(shí)候琉兜。
- 當(dāng)你Resize窗口的時(shí)候(移動(dòng)端沒(méi)有這個(gè)問(wèn)題),或是滾動(dòng)的時(shí)候毙玻。
- 當(dāng)你修改網(wǎng)頁(yè)的默認(rèn)字體時(shí)豌蟋。
注:display:none會(huì)觸發(fā)reflow,而visibility:hidden只會(huì)觸發(fā)repaint桑滩,因?yàn)闆](méi)有發(fā)現(xiàn)位置變化梧疲。
多說(shuō)兩句關(guān)于滾屏的事,通常來(lái)說(shuō)运准,如果在滾屏的時(shí)候幌氮,我們的頁(yè)面上的所有的像素都會(huì)跟著滾動(dòng),那么性能上沒(méi)什么問(wèn)題胁澳,因?yàn)槲覀兊娘@卡對(duì)于這種把全屏像素往上往下移的算法是很快该互。但是如果你有一個(gè)fixed的背景圖,或是有些Element不跟著滾動(dòng)韭畸,有些Elment是動(dòng)畫(huà)慢洋,那么這個(gè)滾動(dòng)的動(dòng)作對(duì)于瀏覽器來(lái)說(shuō)會(huì)是相當(dāng)相當(dāng)痛苦的一個(gè)過(guò)程塘雳。你可以看到很多這樣的網(wǎng)頁(yè)在滾動(dòng)的時(shí)候性能有多差。因?yàn)闈L屏也有可能會(huì)造成reflow普筹。
基本上來(lái)說(shuō)败明,reflow有如下的幾個(gè)原因:
- Initial。網(wǎng)頁(yè)初始化的時(shí)候太防。
- Incremental妻顶。一些Javascript在操作DOM Tree時(shí)。
- Resize蜒车。其些元件的尺寸變了讳嘱。
- StyleChange。如果CSS的屬性發(fā)生變化了酿愧。
- Dirty沥潭。幾個(gè)Incremental的reflow發(fā)生在同一個(gè)frame的子樹(shù)上。
好了嬉挡,我們來(lái)看一個(gè)示例吧:
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!'));
當(dāng)然钝鸽,我們的瀏覽器是聰明的,它不會(huì)像上面那樣庞钢,你每改一次樣式拔恰,它就reflow或repaint一次。一般來(lái)說(shuō)基括,瀏覽器會(huì)把這樣的操作積攢一批颜懊,然后做一次reflow,這又叫異步reflow或增量異步reflow风皿。但是有些情況瀏覽器是不會(huì)這么做的河爹,比如:resize窗口,改變了頁(yè)面默認(rèn)的字體桐款,等昌抠。對(duì)于這些操作,瀏覽器會(huì)馬上進(jìn)行reflow鲁僚。
但是有些時(shí)候,我們的腳本會(huì)阻止瀏覽器這么干裁厅,比如:如果我們請(qǐng)求下面的一些DOM值:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
- IE中的 getComputedStyle(), 或 currentStyle
因?yàn)楸常绻覀兊某绦蛐枰@些值,那么瀏覽器需要返回最新的值执虹,而這樣一樣會(huì)flush出去一些樣式的改變拓挥,從而造成頻繁的reflow/repaint。
減少reflow/repaint
下面是一些Best Practices:
1)不要一條一條地修改DOM的樣式袋励。與其這樣侥啤,還不如預(yù)先定義好css的class当叭,然后修改DOM的className。
// bad
var
left = 10,
top = 10;
el.style.left = left +"px";
el.style.top = top +"px";
// Good
el.className +=" theclassname";
// Good
el.style.cssText +="; left: "+ left +"px; top: "+ top +"px;";
2)把DOM離線(xiàn)后修改盖灸。如:
- 使用documentFragment 對(duì)象在內(nèi)存里操作DOM
- 先把DOM給display:none(有一次reflow)蚁鳖,然后你想怎么改就怎么改。比如修改100次赁炎,然后再把他顯示出來(lái)醉箕。
- clone一個(gè)DOM結(jié)點(diǎn)到內(nèi)存里,然后想怎么改就怎么改率翅,改完后临谱,和在線(xiàn)的那個(gè)的交換一下趾唱。
3)不要把DOM結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。不然這會(huì)導(dǎo)致大量地讀寫(xiě)這個(gè)結(jié)點(diǎn)的屬性己英。
4)盡可能的修改層級(jí)比較低的DOM。當(dāng)然吴旋,改變層級(jí)比較底的DOM有可能會(huì)造成大面積的reflow损肛,但是也可能影響范圍很小。
5)為動(dòng)畫(huà)的HTML元件使用fixed或absoult的position邮府,那么修改他們的CSS是不會(huì)reflow的荧关。
6)千萬(wàn)不要使用table布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動(dòng)會(huì)造成整個(gè)table的重新布局褂傀。
In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.
Fixed layout, CSS 2.1 Specification
This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.
Automatic layout, CSS 2.1 Specification
幾個(gè)工具和幾篇文章
有時(shí)候忍啤,你會(huì)也許會(huì)發(fā)現(xiàn)在IE下,你不知道你修改了什么東西仙辟,結(jié)果CPU一下子就上去了到100%同波,然后過(guò)了好幾秒鐘repaint/reflow才完成,這種事情以IE的年代時(shí)經(jīng)常發(fā)生叠国。所以未檩,我們需要一些工具幫我們看看我們的代碼里有沒(méi)有什么不合適的東西。
- Chrome下粟焊,Google的SpeedTracer是個(gè)非常強(qiáng)悍的工作讓你看看你的瀏覽渲染的成本有多大冤狡。其實(shí)Safari和Chrome都可以使用開(kāi)發(fā)者工具里的一個(gè)Timeline的東東。
- Firefox下這個(gè)基于Firebug的叫Firebug Paint Events的插件也不錯(cuò)项棠。
- IE下你可以用一個(gè)叫dynaTrace的IE擴(kuò)展悲雳。
最后,別忘了下面這幾篇提高瀏覽器性能的文章:
- Google – Web Performance Best Practices
- Yahoo – Best Practices for Speeding Up Your Web Site
- Steve Souders – 14 Rules for Faster-Loading Web Sites
參考
- David Baron的演講:Fast CSS: How Browsers Lay Out Web Pages:slideshow, all slides, audio (MP3), Session page, Lanyrd page
How Browsers Work:
http://taligarsiel.com/Projects/howbrowserswork1.htmMozilla 的 Style System Overview:
https://developer.mozilla.org/en-US/docs/Style_System_OverviewMozilla的Note of reflow:
http://www-archive.mozilla.org/newlayout/doc/reflow.htmlRendering: repaint, reflow/relayout, restyle:
http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/Effective Rendering CSS:http://css-tricks.com/efficiently-rendering-css/
****Webkit Rendering文檔:http://trac.webkit.org/wiki/WebCoreRendering
轉(zhuǎn)載
PS: 若你覺(jué)得可以香追、還行合瓢、過(guò)得去、甚至不太差的話(huà)透典,可以“關(guān)注”一下晴楔,就此謝過(guò)!