瀏覽器的內核是指支持瀏覽器運行的最核心的程序,分為JS引擎和渲染引擎兩個部分。
頁面加載完成的過程
在網頁的地址欄中輸入url后,瀏覽器經歷了什么樣的過程?
- 客戶端根據DNS服務器得到域名對應的ip地址;
- 客戶端向該ip地址發送http請求;
- 服務器端收到、處理並返回http請求;
- 客戶端收到響應並返回內容。
- 客戶端渲染
瀏覽器收到的其實就是HTML文件,只有HTML格式瀏覽器才能正確解析。接下來就是瀏覽器的渲染過程。
頁面渲染過程
HTML渲染過程主要分為以下部分:
1、解析HTML,生成DOM樹;
2、解析CSS,生成CSS規則樹;
3、合並DOM樹和CSS規則樹,生成render樹;
4、布局render樹(layout/reflow);
5、繪制render樹(print),繪制頁面像素信息;
6、瀏覽器將各層的信息發送給GUI,GUI將各層合成,顯示在屏幕上。
構建DOM樹
瀏覽器根據一定的規則將HTML轉換為DOM樹,大致可以分為幾個步驟:
- 網絡中傳輸的內容其實是
0
和1
這種字節數據,瀏覽器在收到字節數據后,才將字節數據轉換為字符串; - 當數據轉換為字符串以后,瀏覽器會先將這些字符串通過詞法分析轉換為標記(token),這一過程叫做標記化。Token中會標識出當前Token是“開始標簽”還是“結束標簽”亦或是“文本”等信息。
- 結束化結束之后,這些標記緊接着就會被轉換為Node,這些Node會根據不同Node之前的聯系生成DOM樹(Document Object Model)。
除了HTML文件還有CSS文件和JS文件。
構建CSSOM樹
構建CSSOM樹(CSS Object Model)的過程與構建DOM樹是極其相似的。
在這個過程中,瀏覽器會確定下一個節點的樣式,並且這個過程是非常消耗資源的。因為節點的樣式可以直接設置,也可以通過繼承獲得,瀏覽器必須遞歸CSSOM樹才能確定具體的元素的樣式。
構建渲染樹
當生成DOM樹和CSSOM樹之后,下一步就是將這兩棵樹組合為渲染樹。
構建渲染樹並不是簡單的將兩棵樹合並起來。渲染樹只會包括需要顯示的節點和這些節點的樣式信息,如果某個節點是display: none
的樣式,那就不會構建到渲染樹中。
那么,瀏覽器在渲染過程中遇到JS文件會怎么處理?
在渲染過程中,如果遇到<script>
就停止渲染,執行JS代碼。因為瀏覽器有GUI渲染線程和JS引擎線程,這兩個線程是互斥的,JavaScript的加載、解析和執行會阻塞渲染。
面試題:“為什么大家普遍把
<script scr=""></script>
這樣的代碼放在body最底部?JS文件不止會阻塞DOM的構建,也會導致CSSOM的構建。不完整的CSSOM是無法使用的,JavaScript想要訪問CSSOM並更改它,就必須得到完整的CSSOM。所以導致瀏覽器在未完成CSSOM的構建的時候想要運行JavaScript。這種情況下,瀏覽器會先下載和構建CSSOM,然后再執行JavaScript。
<script>
標簽必須放在底部嗎?並不是必須放在底部,我們可以為script標簽添加屬性:
defer
屬性,表示js文件會並行下載,但是會放到HTML解析完成后順序執行。async
屬性,對於沒有任何依賴的js文件可以使用,表示JS文件下載和解析不會阻塞渲染。async與defer的區別在於,如果已經加載好,就會開始執行,即使仍在HTML解析階段,所以這種方式加載的JavaScript依然會阻塞load事件。
async-scrapt可能在DOMContentLoaded觸發直線或之后執行,但一定在load之前執行,所以多個async-script的執行順序是不確定的。
布局和繪制
在這個過程中,瀏覽器要弄清楚各個節點在頁面中的確切位置和大小,通常這一行為也被成為自動重排。
布局流程的輸出是一個“盒模型”,它會精確的捕獲每個元素在窗口的確切位置和大小,所有相對測量值,都會轉換為絕對值。
布局完成后,瀏覽器立即發出“Print Setup”和“Paint”事件,將渲染樹轉換成屏幕上的元素。
重繪(Repaint)和回流(reflow)
- 重繪是當前節點需要更改外觀而不會影響布局的,比如改變color屬性。
- 回流是布局或者幾何屬性需要改變。
回流必定發生重繪,重繪不一定發生回流。回流所需要的成本遠大於重繪,因為回流很可能會導致跟該節點相關的很多節點的回流。
會導致性能問題的操作:
- 改變window大小
- 改變字體
- 添加和刪除樣式
- 文字改動
- 定位或者浮動
- 盒模型
因為很多操作都會消耗GPU,所以我們需要規避一些操作減少重繪和回流的次數:
- 使用
transfrom
代替top - 使用
visibility
代替display: none
(前者引起重繪,后者引起回流) - 不要把節點的屬性值放在一個循環里當成循環的變量
- 不要使用table布局(小改動可能造成整個table重新布局)
- CSS選擇符從右往左匹配查找,避免節點層級過多
- 動畫實現的速度的選擇,動畫速度越快,回流次數越多,或者選擇使用
requestAnimationFrame
- 將頻繁重繪或回流的節點設置為圖層,圖層能夠阻止該節點影響到別的節點。
總結
知道了這么多東西,我們會選擇一些優化策略:
1、從文件大小考慮
2、將css放在頭部,將js放在尾部
3、減少資源請求數量
4、下載的內容是否要在首屏上使用
5、script標簽的使用加defer或async屬性。