一、瀏覽器的主要功能
瀏覽器的主要功能是將用戶選擇的web資源呈現(xiàn)出來,它需要從服務(wù)器請(qǐng)求資源,并將其顯示在瀏覽器窗口中,資源的格式通常是HTML,也包括PDF、image及其他格式。用戶用URI(Uniform Resource Identifier統(tǒng)一資源標(biāo)識(shí)符)來指定所請(qǐng)求資源的位置,通過DNS查詢,將網(wǎng)址轉(zhuǎn)換為IP地址。整個(gè)瀏覽器工作的流程,之前博客中有論述:
1、輸入網(wǎng)址。
2、瀏覽器查找域名的IP地址。
3. 瀏覽器給web服務(wù)器發(fā)送一個(gè)HTTP請(qǐng)求
4. 網(wǎng)站服務(wù)的永久重定向響應(yīng)
5. 瀏覽器跟蹤重定向地址 現(xiàn)在,瀏覽器知道了要訪問的正確地址,所以它會(huì)發(fā)送另一個(gè)獲取請(qǐng)求。
6. 服務(wù)器“處理”請(qǐng)求,服務(wù)器接收到獲取請(qǐng)求,然后處理并返回一個(gè)響應(yīng)。
7. 服務(wù)器發(fā)回一個(gè)HTML響應(yīng)
8. 瀏覽器開始顯示HTML
9. 瀏覽器發(fā)送請(qǐng)求,以獲取嵌入在HTML中的對(duì)象。在瀏覽器顯示HTML時(shí),它會(huì)注意到需要獲取其他地址內(nèi)容的標(biāo)簽。這時(shí),瀏覽器會(huì)發(fā)送一個(gè)獲取請(qǐng)求來重新獲得這些文件。這些文件就包括CSS/JS/圖片等資源,這些資源的地址都要經(jīng)歷一個(gè)和HTML讀取類似的過程。所以瀏覽器會(huì)在DNS中查找這些域名,發(fā)送請(qǐng)求,重定向等等…
那么,一個(gè)頁(yè)面,究竟是如何從我們輸入一個(gè)網(wǎng)址到最后完整的呈現(xiàn)在我們面前的呢?還需要了解一下瀏覽器是如何渲染的:
二、瀏覽器的渲染
下面是渲染引擎在取得內(nèi)容之后的基本流程:
解析html以構(gòu)建dom樹 -> 構(gòu)建render樹 -> 布局render樹 -> 繪制render樹
如圖:
所以,瀏覽器會(huì)解析三個(gè)東西:
(1) HTML/SVG/XHTML,解析這三種文件會(huì)產(chǎn)生一個(gè) DOM Tree。
(2) CSS,解析 CSS 會(huì)產(chǎn)生 CSS 規(guī)則樹。
(3) Javascript腳本,主要是通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree.
到底是怎么解析怎么渲染的,我的疑問在于,瀏覽器到底是先解析生成了DOM樹,然后再加載CSS JS文件進(jìn)行渲染,還是在生成DOM的過程中,遇到了 link script 然后就加載CSS JS,邊加載邊渲染。我有這種疑問的原因在于,看網(wǎng)上的帖子,說的根本不一樣好嘛! 比如這篇 我想說,這個(gè)寫的讓我直接懵逼,真的是直接懵逼啊,學(xué)習(xí)的過程中,總會(huì)遇到困難,但這次,讓我真的好難啊。不過正因?yàn)椴欢爬^續(xù)查資料繼續(xù)學(xué)習(xí)嘛 ==!我又查了一上午,自己測(cè)試測(cè)試測(cè)試,然后覺著,我好像是明白點(diǎn)了。真的推薦大家去認(rèn)真看《how browsers work》這篇文章,學(xué)習(xí)不懂得知識(shí)的時(shí)候,還是要從比較權(quán)威的資料看起比較好,也不要像我今天這樣,無(wú)頭蒼蠅亂查。
上述這個(gè)過程是逐步完成的,為了更好的用戶體驗(yàn),渲染引擎將會(huì)盡可能早的將內(nèi)容呈現(xiàn)到屏幕上,并不會(huì)等到所有的html都解析完成之后再去構(gòu)建和布局render樹。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容,同時(shí),可能還在通過網(wǎng)絡(luò)下載其余內(nèi)容。(這段話是《how browsers work》里面講的,讓我茅塞頓開)
幾個(gè)概念:
(1)Reflow(回流):瀏覽器要花時(shí)間去渲染,當(dāng)它發(fā)現(xiàn)了某個(gè)部分發(fā)生了變化影響了布局,那就需要倒回去重新渲染。
(2)Repaint(重繪):如果只是改變了某個(gè)元素的背景顏色,文字顏色等,不影響元素周圍或內(nèi)部布局的屬性,將只會(huì)引起瀏覽器的repaint,重畫某一部分。
Reflow要比Repaint更花費(fèi)時(shí)間,也就更影響性能。所以在寫代碼的時(shí)候,要盡量避免過多的Reflow。
reflow的原因:
(1)頁(yè)面初始化的時(shí)候;
(2)操作DOM時(shí);
(3)某些元素的尺寸變了;
(4)如果 CSS 的屬性發(fā)生變化了。
減少 reflow/repaint
(1)不要一條一條地修改 DOM 的樣式。與其這樣,還不如預(yù)先定義好 css 的 class,然后修改 DOM 的 className。
(2)不要把 DOM 結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。
(3)為動(dòng)畫的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他們的 CSS 是不會(huì) reflow 的。
(4)千萬(wàn)不要使用 table 布局。因?yàn)榭赡芎苄〉囊粋€(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局。
我應(yīng)該是已經(jīng)把網(wǎng)上所有的關(guān)于瀏覽器加載 解析 渲染過程的文章都看全了,其中寫的比較好的一個(gè)版本是下面這個(gè):
HTML頁(yè)面加載和解析流程
1. 用戶輸入網(wǎng)址(假設(shè)是個(gè)html頁(yè)面,并且是第一次訪問),瀏覽器向服務(wù)器發(fā)出請(qǐng)求,服務(wù)器返回html文件;
2. 瀏覽器開始載入html代碼,發(fā)現(xiàn)<head>標(biāo)簽內(nèi)有一個(gè)<link>標(biāo)簽引用外部CSS文件;
3. 瀏覽器又發(fā)出CSS文件的請(qǐng)求,服務(wù)器返回這個(gè)CSS文件;
4. 瀏覽器繼續(xù)載入html中<body>部分的代碼,并且CSS文件已經(jīng)拿到手了,可以開始渲染頁(yè)面了;
5. 瀏覽器在代碼中發(fā)現(xiàn)一個(gè)<img>標(biāo)簽引用了一張圖片,向服務(wù)器發(fā)出請(qǐng)求。此時(shí)瀏覽器不會(huì)等到圖片下載完,而是繼續(xù)渲染后面的代碼;
6. 服務(wù)器返回圖片文件,由于圖片占用了一定面積,影響了后面段落的排布,因此瀏覽器需要回過頭來重新渲染這部分代碼;
7. 瀏覽器發(fā)現(xiàn)了一個(gè)包含一行Javascript代碼的<script>標(biāo)簽,趕快運(yùn)行它;
8. Javascript腳本執(zhí)行了這條語(yǔ)句,它命令瀏覽器隱藏掉代碼中的某個(gè)<div> (style.display=”none”)。突然少了這么一個(gè)元素,瀏覽器不得不重新渲染這部分代碼;
9. 終于等到了<ml>的到來,瀏覽器淚流滿面……
10. 等等,還沒完,用戶點(diǎn)了一下界面中的“換膚”按鈕,Javascript讓瀏覽器換了一下<link>標(biāo)簽的CSS路徑;
11. 瀏覽器召集了在座的各位<div><span><ul><li>們,“大伙兒收拾收拾行李,咱得重新來過……”,瀏覽器向服務(wù)器請(qǐng)求了新的CSS文件,重新渲染頁(yè)面。
與討論主題相關(guān)的其他思考
編寫CSS時(shí)應(yīng)該注意:
CSS選擇符是從右到左進(jìn)行匹配的。從右到左!所以,#nav li 我們以為這是一條很簡(jiǎn)單的規(guī)則,秒秒鐘就能匹配到想要的元素,但是,但是,但是,是從右往左匹配啊,所以,會(huì)去找所有的li,然后再去確定它的父元素是不是#nav。,因此,寫css的時(shí)候需要注意:
dom深度盡量淺。
減少inline javascript、css的數(shù)量。
使用現(xiàn)代合法的css屬性。
不要為id選擇器指定類名或是標(biāo)簽,因?yàn)閕d可以唯一確定一個(gè)元素。
避免后代選擇符,盡量使用子選擇符。原因:子元素匹配符的概率要大于后代元素匹配符。后代選擇符;#tp p{} 子選擇符:#tp>p{}
避免使用通配符,舉一個(gè)例子,.mod .hd *{font-size:14px;} 根據(jù)匹配順序,將首先匹配通配符,也就是說先匹配出通配符,然后匹配.hd(就是要對(duì)dom樹上的所有節(jié)點(diǎn)進(jìn)行遍歷他的父級(jí)元素),然后匹配.mod,這樣的性能耗費(fèi)可想而知.
關(guān)于script標(biāo)簽的位置
現(xiàn)在,我們大都會(huì)將script標(biāo)簽放在body結(jié)束標(biāo)簽之前,那原因是什么呢?我今天也做了一個(gè)測(cè)試。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>測(cè)試js代碼位置</title>
<script type="text/javascript">
var item = document.getElementById("item");
cosole.log(item);
</script>
</head>
<body>
<div id="item" width="100px" height="100px">
你好
</div>
</body>
<ml>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
上述代碼中有一段js代碼,要在控制臺(tái)打印一個(gè)元素,我把script標(biāo)簽放在head里,控制臺(tái)里打印出來的是null。
一直在執(zhí)行那個(gè)打印1的死循環(huán),后面的body都沒有加載渲染出來。所以,這個(gè)小例子,我們可以看出,js的下載和執(zhí)行會(huì)阻塞Dom樹的構(gòu)建。
所以,Javascript的加載和執(zhí)行的特點(diǎn):
(1)載入后馬上執(zhí)行;
(2)執(zhí)行時(shí)會(huì)阻塞頁(yè)面后續(xù)的內(nèi)容(包括頁(yè)面的渲染、其它資源的下載)。原因:因?yàn)闉g覽器需要一個(gè)穩(wěn)定的DOM樹結(jié)構(gòu),而JS中很有可能有 代碼直接改變了DOM樹結(jié)構(gòu),比如使用 document.write 或 appendChild,甚至是直接使用的location.href進(jìn)行跳轉(zhuǎn),瀏覽器為了防止出現(xiàn)JS修 改DOM樹,需要重新構(gòu)建DOM樹的情況,所以 就會(huì)阻塞其他的下載和呈現(xiàn)。
減少 JavaScript 對(duì)性能的影響的方法:
將所有的script標(biāo)簽放到頁(yè)面底部,也就是body閉合標(biāo)簽之前,這能確保在腳本執(zhí)行前頁(yè)面已經(jīng)完成了DOM樹渲染。
盡可能地合并腳本。頁(yè)面中的script標(biāo)簽越少,加載也就越快,響應(yīng)也越迅速。無(wú)論是外鏈腳本還是內(nèi)嵌腳本都是如此。
采用無(wú)阻塞下載 JavaScript 腳本的方法:
(1)使用script標(biāo)簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版本);
(2)使用動(dòng)態(tài)創(chuàng)建的script元素來下載并執(zhí)行代碼;