1 大緻流程
浏覽器端會把HTML解析成DOM樹,CSS檔案解析成CSSOM =》 兩者解析完成後 DOM 和 CSSOM合并 =》 浏覽器會進一步解析為Render Tree =》 浏覽器計算每個元素在頁面上位置和大小(Layout) =》 最後繪制到頁面上 (Paint )
2 步驟詳解
2.1 解析html
輸入網址後,浏覽器發起請求,首先得到的是html文檔,當我們的浏覽器獲得html檔案後,浏覽器會自上而下的加載,通過HTML Parser解析器對html文檔進行解析。
加載說的就是擷取資源檔案的過程,如果在加載的過程中,遇到外部css檔案和圖檔,浏覽器會另外發出一個請求,來擷取css檔案和相應的圖檔,這個請求是異步的,并不會影響html檔案。
但是如果遇到javascript檔案,html檔案會挂起渲染的線程,等待javascript加載完畢後,html檔案再繼續渲染。
html代碼中是如何生成DOM樹的:
用如下代碼示範生成DOM樹的過程:
<html>
<body>
<h1>HelloWorld</h1>
<div>
<div>
<p>picture:</p>
<img src="example.png"/>
</div>
<div>
<p>A paragraph of explanatory text...</p>
</div>
</div>
</body>
</html>
首先樹建構器接收到标簽解析器發來的起始标簽名後,會加入到棧中,圖1是解析到
<h1>
标簽的棧中壓入的内容,共有
<html><body><h1>
三個标簽,此時還未向DOM樹中添加任何結點(圖中黑色實線框代表開始标簽,紅色虛線框代表結束标簽,結束标簽不會入棧)。
繼續向下解析,接收到一個結束标簽,此時查詢棧頂元素,如果和傳入的結束标簽屬于同種類型的p标簽(如圖2),則将棧頂元素彈出,向DOM樹中加入此節點,然後繼續向下解析(如圖3)。
如果遇到的是沒有封閉标簽的元素如,則直接加入DOM樹中即可,無需入棧。
依次向下解析,當棧為空,即根節點也加入到DOM樹中,DOM樹建構完畢。
當某個元素缺失結束标簽時,會怎麼樣?浏覽器會自動補一個結束标簽。
2.2 css解析
遇到内聯的css樣式就開始解析,外部的css檔案,則在接收到了之後通過CSS Parse解析器進行解析。最終把css語句解析成為CSSOM(CSS Object Model)。
在解析css的同時也在解析dom,兩者是并發的,是以等到css解析完畢就會逐漸的渲染頁面了。
當所有的css檔案下載下傳完且所有的CSSOM建構結束後,就會和DOM一起生成Render Tree。
是以,頁面渲染依賴于css的加載。
為了形成Render Tree,浏覽器大緻做的事情有:
從DOM樹根節點開始,周遊每一個可見的節點
一些節點是完全不可見的(比如 script标簽,meta标簽等),這些節點會被忽略,因為他們不會影響渲染的輸出
一些節點是通過CSS樣式隐藏了,這些節點同樣被忽略——樣式是display:none;
對每一個可見的節點,找到合适的比對的CSSOM規則,并且應用樣式
注意:“visibility:hidden”和“display:none”是不同的,“visibility:hidden”将元素設定為不可見,但是同樣在布局上占領一定空間(例如,它會被渲染成為空盒子),但是“display:none”的元素不會挂載到render tree上,不是布局中的一部分 。
可參考下圖對比其關系:
2.3 js解析
html加載過程中 ,遇到js就立即解析下載下傳執行。
當浏覽器遇到一個 script 标記時,DOM 建構将暫停,直至腳本完成執行,然後繼續建構DOM。每次去執行JavaScript腳本都會嚴重地阻塞DOM樹的建構,如果JavaScript腳本還操作了CSSOM,而正好這個CSSOM還沒有下載下傳和建構,浏覽器甚至會延遲腳本執行和建構DOM,直至完成其CSSOM的下載下傳和建構。
為什麼html需要等待javascript呢?因為javascript可能會修改DOM,導緻後續的html資源白白加載,是以html必須等待javascript檔案加載完畢後,再繼續渲染。這也就是為什麼javascript檔案要寫在底部body标簽前的原因。
是以,script 标簽的位置很重要。實際使用時,可以遵循下面兩個原則:
CSS 優先:引入順序上,CSS 資源先于 JavaScript 資源。
JS置後:我們通常把JS代碼放到頁面底部,且JavaScript 應盡量少影響 DOM 的建構。
如果是外部的,還可以在
<script>
标簽上加上defer或async屬性
- defer可以讓js的下載下傳不影響html的後續解析,且在html解析完了再執行js檔案,且按照原來的下載下傳順序
- async也是讓js的下載下傳不影響html的後續解析,但一旦下載下傳完了就立即執行,是以也無法保證按照下載下傳順序執行
css解析和js解析時互斥的,css阻塞js的執行,但是不會阻塞外部腳本的加載。
js邏輯對于dom節點的依賴關系,因為js可能會影響到render tree,為了避免浪費浏覽器資源,執行js代碼時,dom和css解析都會被當機,js執行完了,才能執行後續的内容。
但是,當JS執行時間過長會頁面阻塞,那麼JS就真的對CPU密集型計算無能為力麼?
是以,後來HTML5中支援了 Web Worker。
通過使用Web Workers,Web應用程式可以在獨立于主線程的背景線程中,運作一個腳本操作。這樣做的好處是可以在獨立線程中執行費時的處理任務,進而允許主線程(通常是UI線程)不會是以被阻塞/放慢。
2.4 回流
概念:當render tree中的一部分(或全部)因為元素的規模尺寸,布局,隐藏等改變而需要重新建構。這就稱為回流(reflow)
當頁面布局、元素幾何屬性發生變化時 就會發生回流,此時我們需要重新驗證并計算渲染樹。是以如何回流過于頻繁,也就會增加浏覽器的工作量,降低了浏覽器性能。
觸發頁面重布局的屬性
- 盒子模型相關屬性會觸發重布局
- 定位屬性及浮動也會觸發重布局
- 改變節點内部文字結構也會觸發重布局
2.5 重繪
概念:當render tree中的一些元素需要更新屬性,而這些屬性隻是影響元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪。
隻觸發重繪的屬性
關系:當重繪時不一定會發生回流,但是回流時一定是需要對頁面進行重繪。
3 性能優化
從性能優化的角度,減少浏覽器的計算,盡量減少浏覽器的重排和重繪,尤其是重排。
- 避免使用通配規則。如 *{} 計算次數驚人!隻對需要用到的元素進行選擇。
- 避免設定大量的style屬性,因為通過設定style屬性改變結點樣式的話,每一次設定都會觸發一次reflow,是以最好是使用class屬性。
- 實作元素的動畫,它的position屬性,最好是設為absoulte或fixed,這樣不會影響其他元素的布局。
- 動畫實作的速度的選擇。比如實作一個動畫,以1個像素為機關移動這樣最平滑,但是reflow就會過于頻繁,大量消耗CPU資源,如果以3個像素為機關移動則會好很多。
- 不要使用table布局,因為table中某個元素旦觸發了reflow,那麼整個table的元素都會觸發reflow。那麼在不得已使用table的場合,可以設定table-layout:auto;或者是table-layout:fixed這樣可以讓table一行一行的渲染,這種做法也是為了限制reflow的影響範圍。
- 如果CSS裡面有計算表達式,每次都會重新計算一遍,觸發一次reflow
部分摘自:
HTML文檔解析和DOM樹的建構
借助performance工具直覺了解浏覽器的渲染過程