瀏覽器的渲染過(guò)程
1,瀏覽器解析html源碼,然后創(chuàng)建一個(gè) DOM樹。在DOM樹中,每一個(gè)HTML標(biāo)簽都有一個(gè)對(duì)應(yīng)的節(jié)點(diǎn),并且每一個(gè)文本也都會(huì)有一個(gè)對(duì)應(yīng)的文本節(jié)點(diǎn)。DOM樹的根節(jié)點(diǎn)就是 documentElement,對(duì)應(yīng)的是html標(biāo)簽。
2,瀏覽器解析CSS代碼,計(jì)算出最終的樣式數(shù)據(jù)。對(duì)CSS代碼中非法的語(yǔ)法她會(huì)直接忽略掉。解析CSS的時(shí)候會(huì)按照如下順序來(lái)定義優(yōu)先級(jí):瀏覽器默認(rèn)設(shè)置,用戶設(shè)置,外鏈樣式,內(nèi)聯(lián)樣式,html中的style。
3,構(gòu)建出DOM樹,并且計(jì)算出樣式數(shù)據(jù)后,下一步就是構(gòu)建一個(gè) 渲染樹(rendering tree)。渲染樹和DOM樹有點(diǎn)像,但是是有區(qū)別的。DOM樹完全和html標(biāo)簽一一對(duì)應(yīng),但是渲染樹會(huì)忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一個(gè)行在渲染樹中都是獨(dú)立的一個(gè)節(jié)點(diǎn)。渲染樹中的每一個(gè)節(jié)點(diǎn)都存儲(chǔ)有對(duì)應(yīng)的css屬性。
4,一旦渲染樹創(chuàng)建好了,瀏覽器就可以根據(jù)渲染樹直接把頁(yè)面繪制到屏幕上。一個(gè)渲染過(guò)程的例子
例如有下面這樣一段HTML代碼:
每個(gè)頁(yè)面至少在初始化的時(shí)候會(huì)有一次重排操作。任何對(duì)渲染樹的修改都有可能會(huì)導(dǎo)致下面兩種操作:
1,重排就是渲染樹的一部分必須要更新 并且節(jié)點(diǎn)的尺寸發(fā)生了變化。這就會(huì)觸發(fā)重排操作。
2,重繪部分節(jié)點(diǎn)需要更新,但是沒有改變他的集合形狀,比如改變了背景顏色,這就會(huì)觸發(fā)重繪。什么情況下會(huì)觸發(fā)重繪或重排
下面任何操作都會(huì)觸發(fā)重繪或者重排:增加或刪除DOM節(jié)點(diǎn)設(shè)置 display: none;(重排并重繪) 或者 visibility: hidden(只有重排)移動(dòng)頁(yè)面中的元素增加或者修改樣式用戶 改變窗口大小滾動(dòng)頁(yè)面等
看一個(gè)例子

有些重繪操作會(huì)比其他操作昂貴很多。比如你把一個(gè)body的子元素做了修改,不一定會(huì)導(dǎo)致大量的其他節(jié)點(diǎn)更新,但是你把一個(gè)元素移動(dòng)到頁(yè)面頂部去,可能就會(huì)導(dǎo)致全部其他節(jié)點(diǎn)進(jìn)行重排操作,這個(gè)代價(jià)就非常昂貴。聰明的瀏覽器
因?yàn)殇秩緲涞母淖儗?dǎo)致的重繪或重排操作都可能代價(jià)很高,瀏覽器會(huì)對(duì)這個(gè)改動(dòng)做很多優(yōu)化。一個(gè)策略就是不要立即做操作,而是批量進(jìn)行。比如把你的腳本對(duì)DOM的修改放入一個(gè)隊(duì)列,在隊(duì)列所有操作結(jié)束后只需要進(jìn)行一次繪制即可。但是有的時(shí)候腳本可能會(huì)導(dǎo)致瀏覽器的批量?jī)?yōu)化無(wú)法進(jìn)行,可能在清空隊(duì)列之前就需要重新繪制(繪制意思是重繪或者重排)頁(yè)面。比如你通過(guò)腳本獲取這些樣式:offsetTop, offsetLeft, offsetWidth, offsetHeightscrollTop/Left/Width/HeightclientTop/Left/Width/HeightgetComputedStyle(), or currentStyle in IE因?yàn)闉g覽器必須給你最新的值,所以當(dāng)你進(jìn)行這些取值操作的時(shí)候會(huì)立刻觸發(fā)一次頁(yè)面的繪制。這樣本來(lái)可以批量修改樣式然后一次性繪制的方法就無(wú)法使用了。減少重繪和重排
1,不要一個(gè)一個(gè)地單獨(dú)修改屬性,最好通過(guò)一個(gè)classname來(lái)定義這些修改//
badvar left = 10,
top = 10;
el.style.left = left + "px";
el.style.top = top + "px"; // better el.className += " theclassname";
2,把對(duì)節(jié)點(diǎn)的大量修改操作放在頁(yè)面之外用 documentFragment來(lái)做修改clone 節(jié)點(diǎn),在clone之后的節(jié)點(diǎn)中做修改,然后直接替換掉以前的節(jié)點(diǎn)通過(guò) display: none 來(lái)隱藏節(jié)點(diǎn)(直接導(dǎo)致一次重排和重繪),做大量的修改,然后顯示節(jié)點(diǎn)(又一次重排和重繪),總共只會(huì)有兩次重排。
3,不要頻繁獲取計(jì)算后的樣式。如果你需要使用計(jì)算后的樣式,最好暫存起來(lái)而不是直接從DOM上讀取。
4,總的來(lái)說(shuō),總是考慮到渲染樹得存在,考慮到你的一次修改會(huì)導(dǎo)致多大的繪制操作。比如絕對(duì)定位元素的動(dòng)畫就不會(huì)影響其他大部分元素。
js和css對(duì)瀏覽器渲染文檔的阻塞
網(wǎng)頁(yè)中引用的外部文件: JavaScritp、CSS 等常常會(huì)阻塞瀏覽器渲染頁(yè)面。假設(shè)在 <head> 中引用的某個(gè) JavaScript 文件由于各種不給力需要2秒來(lái)加載,那么瀏覽器渲染頁(yè)面的過(guò)程就會(huì)被阻塞2秒,直到該JS文件下載并執(zhí)行完后才繼續(xù)。前端性能調(diào)優(yōu)時(shí)必須排除任何潛在的渲染阻塞點(diǎn),讓瀏覽器在最短時(shí)間內(nèi)渲染出整體頁(yè)面。
js為什么會(huì)阻塞
<!doctype html>
<html>
<head>
<script type="text/javascript" src="page.js"></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
上述代碼中,當(dāng)瀏覽器解析 script 標(biāo)簽時(shí),由于瀏覽器并不知道 page.js 將會(huì)對(duì)頁(yè)面做什么改變,所以瀏覽器需要停止渲染,下載并執(zhí)行 page.js 后再繼續(xù)渲染后面的內(nèi)容。如果 page.js 的下載過(guò)程中出現(xiàn)任何延遲,也將影響整個(gè)頁(yè)面的渲染。
Inline JavaScript
如果頁(yè)面的初始渲染的確依賴于page.js,我們可以考慮使用內(nèi)聯(lián)JavaScript。
<!doctype html>
<html>
<head>
<script type="text/javascript">
/* page.js的內(nèi)容 */
</script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
推遲加載
如果頁(yè)面的初始渲染并不依賴于page.js,我們可以考慮推遲加載page.js,讓其在頁(yè)面初始內(nèi)容渲染完成后再加載。
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello World</h1>
<script type="text/javascript" src="page.js"></script>
</body>
</html>
異步加載
HTML5允許我們給 script 標(biāo)簽添加屬性: "async" 來(lái)告訴瀏覽器不必停下來(lái)等待該腳本執(zhí)行,什么時(shí)候下載完什么時(shí)候執(zhí)行該腳本就可以了。這樣的話瀏覽器會(huì)邊下載page.js邊渲染后面的內(nèi)容。
<!doctype html>
<html>
<head>
<script type="text/javascript" src="page.js" async></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
然而如果某個(gè)JS被其他JS所依賴,那么就不能使用異步加載了。
<!doctype html>
<html>
<head>
<script type="text/javascript" src="jquery-1.11.3.min.js" async></script>
<script type="text/javascript" src="jq-plugin.js" async></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
由于使用異步加載后,JS不再順序執(zhí)行。上例中 jq-plugin.js 依賴于jQuery,如果 jq-plugin.js 先下載完成,此時(shí)jQuery還沒下載完,那么瀏覽器就會(huì)先執(zhí)行 jq-plugin.js 導(dǎo)致出錯(cuò)。當(dāng)然這類問題可以通過(guò)引入依賴管理來(lái)解決,這是另外一個(gè)主題,就不展開討論了。
CSS阻塞
由于CSS決定了DOM元素的樣式、布局,所以瀏覽器遇到CSS文件時(shí)會(huì)等待CSS文件加載并解析完后才繼續(xù)渲染頁(yè)面。
Inline CSS
我們可以將那些頁(yè)面首屏渲染需要用到的CSS代碼加入Inline CSS。
<!doctype html>
<html>
<head>
<style tpe="text/css">
.blue {
color: blue;
}
</style>
</head>
<body>
<div class="blue">
Hello, world!
</div>
</body>
</html>
推遲加載CSS
對(duì)于那些首屏渲染不需要用到的CSS,我們可以依舊使用文件形式并在頁(yè)面內(nèi)容渲染完成后再加載。
<!doctype html>
<html>
<head>
<style tpe="text/css">
.blue {
color: blue;
}
</style>
</head>
<body>
<div class="blue">
Hello, world!
</div>
<link href="other.css" rel="stylesheet" />
</body>
</html>
結(jié)論
在頁(yè)面加載時(shí)我們需要讓頁(yè)面內(nèi)容盡快呈現(xiàn)給用戶,頁(yè)面初始渲染所需要的JS和CSS可以直接在 <head> 標(biāo)簽中以代碼形式插入。所有的外部文件引用可以放在頁(yè)面內(nèi)容之后,對(duì)于JS文件也可以采用異步加載。
實(shí)際的優(yōu)化建議
匯總了一些有用的信息,我建議以下幾點(diǎn):
創(chuàng)建合法的 HTML 和 CSS ,別忘了制定文件編碼,Style 應(yīng)該寫在 head 標(biāo)簽中,script 標(biāo)簽應(yīng)該加載 body 標(biāo)簽結(jié)束的位置。
試著簡(jiǎn)化和優(yōu)化 CSS 選擇器(這個(gè)優(yōu)化點(diǎn)被大多數(shù)使用 CSS 預(yù)處理器的開發(fā)者忽略了)。將嵌套層數(shù)控制在最小。以下是 CSS 選擇器的性能排行(從最快的開始):
ID選擇器:#id
class選擇器: .class
標(biāo)簽: div
相鄰的兄弟元素:a + i
父元素選擇器: ul > li
通配符選擇器: *
偽類和偽元素: a:hover
,你應(yīng)該記住瀏覽器處理選擇器是從右向左的,這也就是為什么最右面的選擇器會(huì)更快——#id
或.class
; html-script: false ]div * {...} // bad
.list li {...} // bad
.list-item {...} // good
#list .list-item {...} // good
在你的腳本中,盡可能的減少 DOM 的操作。把所有東西都緩存起來(lái),包括屬性和對(duì)象(如果它可被重復(fù)使用)。進(jìn)行復(fù)雜的操作的時(shí)候,最好操作一個(gè)“離線”的元素(“離線”元素的意思是與 DOM 對(duì)象分開、僅存在內(nèi)存中的元素),然后將這個(gè)元素插入到 DOM 中。
如果你使用 jQuery,遵循jQuery 選擇器最佳實(shí)踐
要改變?cè)氐臉邮?,修改“class”屬性是最高效的方式之一。你要改變 DOM 樹的層次越深,這一條就越高效(這也有助于將表現(xiàn)和邏輯分開)。
盡可能的只對(duì) position 為 absolute 或 fix 的元素做動(dòng)畫。
當(dāng)滾動(dòng)時(shí)禁用一些復(fù)雜的 :hover
動(dòng)畫是一個(gè)很好的主意(例如,給 body 標(biāo)簽加一個(gè) no-hover 的 class)關(guān)于這個(gè)主題的文章。
參考
http://www.cnblogs.com/chenlogin/p/5221562.html
http://highfly-s.iteye.com/blog/1908259
http://blog.csdn.net/lihongxun945/article/details/37830667
http://joji.me/zh-cn/blog/web-performance-optimization-remove-blocking-javascript-and-css