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