瀏覽器的渲染機制
作者:小土豆biubiubiu
部落格園:www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d
簡書:https://www.jianshu.com/u/cb1c3884e6d5
微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)
碼字不易,點贊鼓勵喲~
一.前言
我們熟知的瀏覽器的主要功能就是向伺服器傳送請求,然後顯示伺服器返回的資源。
而這裡的資源一般指的就是HTML文件。
(當然資源還包括xml、圖片)
那瀏覽器是如何將一個HTML文件解析為我們眼睛看到的各種圖形和顏色呢。
這便是我們要討論的瀏覽器的渲染機制了。
關於瀏覽器的渲染機制,是面試會被問到的高頻考點。
所以話不多說,一起探究一下這個問題。
備註:瀏覽器的渲染機制是由瀏覽器的渲染引擎決定的。
瀏覽器的渲染引擎也就是我們常說的瀏覽器的核心。
比如常見的IE的核心-Trident,Firefox的核心-Gecko,chrome的核心-Webkit等。
由於不同瀏覽器的核心實現存在差異,因此同一份HTML文件在不同瀏覽器上的解析結果也會有細微的差距。
本篇文章不會細緻到不同核心的差異。
二.瀏覽器的渲染執行緒
我們都知道一份HTML文件中一般會包含HTML標籤、CSS樣式、JavaScript指令碼這些基礎的內容。
所以瀏覽器為了處理HTML文件,設計了一系列的執行緒,分別為:
GUI渲染執行緒
JS引擎執行緒
定時觸發器執行緒
事件處理執行緒
非同步http請求執行緒
下面簡單的瞭解一下這些執行緒在解析HTML文件中都分別負責那些任務。
GUI渲染執行緒
GUI-圖形使用者介面,那GUI渲染執行緒也就是負責渲染瀏覽器介面上顯示的內容。
即負責解析HTML標籤和CSS樣式。
在瀏覽器的五個渲染流程中,GUI渲染執行緒均參與其中。
JS引擎執行緒
JS引擎執行緒顧名思義就是負責處理JavaScript指令碼的執行緒。
我們都知道JS存在同步任務和非同步任務。
而JS引擎執行緒是一個單執行緒,所以JS引擎在執行JavaScript程式碼的時候主執行緒會維護一個執行棧,除了執行棧之外還會維護一個任務佇列。
主執行緒的執行棧執行同步任務,當遇到一些非同步任務時,先將非同步任務交給對應的執行緒。
當非同步任務滿足條件需要執行時,會將其推入任務佇列,主執行緒空閒後會順序處理任務佇列中的任務。
(這個執行機制稱為事件迴圈機制)
備註:對於不同核心的瀏覽器,對應的JS引擎也不同。
IE8的JS引擎是Jscript,IE9開始用Chakra。
FireFox不同版本的JS引擎分別為SpiderMonkey(1.0-3.0)/ TraceMonkey(3.5-3.6)/ JaegerMonkey(4.0)。
Chrome的JS引擎是V8引擎。
事件處理執行緒
JS引擎執行緒遇到事件處理程式程式碼塊時,會將這個處理程式新增到事件處理執行緒中。
當事件滿足條件被觸發後,事件處理執行緒會將該事件新增到前面說的JS執行時維護的任務佇列中,之後就交由JavaScript引擎執行緒去處理。
定時觸發器執行緒
處理setTimeout/setInterval程式碼的執行緒就叫定時觸發執行緒。
當定時器被觸發後,會將需要執行的任務新增到JS的任務佇列,當JS引擎執行緒空閒的時候,會按順序執行任務佇列中的任務。
(這裡牽扯到的一個知識點就是定時器被觸發後不會立即就執行我們編寫的定時任務,必須要等到JS引擎空閒且按順序執行任務佇列中的任務)
非同步HTTP請求執行緒
非同步HTTP請求執行緒主要用於處理XMLHttpRequest請求。
當我們的程式需要向伺服器發起請求時,就會交給該執行緒處理。
當請求得到響應後,如果有需要執行的回撥函式,會將回調放入JS的任務佇列,後續在由JS引擎執行緒處理。
基於前面對這些執行緒的簡單描述,我畫了一個簡單的圖來解釋這些執行緒的工作
三.瀏覽器的渲染流程
簡單瞭解過瀏覽器渲染過程中執行的幾個執行緒之後,接著我們就需要詳細去了解瀏覽器的渲染流程。
解析構建DOM樹(DOM Tree)
解析意為解釋分析,即將文件中的程式碼轉化為瀏覽器引擎可以理解的規則或者資料結構。
那解析後的結果是代表文件結構的一個樹,一般將其稱為解析樹或者語法樹。
我們知道一份HTML文件是由html標籤、CSS規則和JavaScript指令碼構成的。
所以在DOM樹的構建過程中,分別會遇到HTML解析、CSS解析和JavaScript指令碼解析(或者外部樣式、指令碼載入)。
我們以下面這份HTML文件為例,簡單的梳理一下文件解析為DOM樹的過程。
<html> <head> <meta chartset="utf-8"> <title>瀏覽器渲染流程</title> <style type="text/css"> .box{ border:1px solid #448aff; } .box span{ width: 100px; height: 100px; border-radius: 50%; } </style> <script type="text/javascript" src="./index.js"></script> </head> <body> <div class="box"> <span class="cicle"></span> </div> </body> </html>
構建DOM樹的過程中,我們需要注意一下幾點:
GUI渲染引擎在處理HTML標籤的同時,也會同步進行CSS規則樹的構建
GUI渲染引擎和JS引擎是互斥的,兩者不能同時進行。當有一個正在執行時,另外一個就需要掛起。
構建CSS規則樹
構建CSS規則樹的過程就是處理CSS樣式的過程,構建CSS規則樹是由CSS直譯器來完成的。
CSS直譯器會將CSS檔案解析為一個StyleSheet物件,StyleSheet物件又包含一個或者多個CSSRule物件。
CSSRule物件則包含選擇器物件Selectors和宣告物件Declaration。
那基於前面文件中的CSS樣式,我們按照規則簡單構建一個CSS規則樹。
CSS直譯器構建CSS規則樹這裡我需要總結幾點:
CSS直譯器會將複合的CSS規則拆分成多個宣告物件(可以參考上圖中border的解析)
CSS規則樹的構建和DOM樹的構建是同步進行的
呈現樹構建(Render Tree)
呈現樹基於DOM樹和CSS規則樹構建的,是文件的視覺化表示。
它的作用是為了後續能將元素進行正確的佈局和繪製。
在呈現樹的構建過程中,總結了以下幾點:
一些非視覺化的元素不會插入到呈現樹中,比如head元素、display為none的元素。
呈現樹構建時會計算每一個視覺化元素的樣式屬性。
樣式屬性計算就是為每一個元素查詢匹配的CSS規則,這個就是根據我們寫的CSS選擇器進行匹配的。
佈局(Layout)
呈現樹構建只是基於DOM樹和CSS規則為元素匹配出了對應的樣式屬性,但是並不會計算出元素在螢幕上從位置和大小。
所以接下來就需要計算元素的位置和大小,我們將其稱為佈局。
佈局階段會遍歷呈現樹,根據呈現樹中每一個節點的資訊(寬、高等樣式屬性)準確計算出每一個節點在頁面上的位置和大小。
繪製(Paint)
繪製階段,瀏覽器引擎會根據呈現樹以及佈局計算出來的元素位置和大小,將元素顯示到螢幕上。
四.總結
到此,瀏覽器中的渲染機制就梳理完成了,基本都是概念性的內容,因此我們在對渲染流程進行一個總結:
解析構建DOM樹
構建CSS規則樹
根據DOM樹和CSS規則樹構建呈現樹
根據呈現樹進行佈局
根據呈現樹和佈局計算出來的位置和大小將元素繪製到瀏覽器中
除了渲染流程,我們還需要關注前面每個過程中需要關注的點。
備註:
在這裡我們需要理解GUI渲染引擎和JS引擎的互斥關係。
我們都知道JS程式碼是可以操作DOM和CSS樣式的。
假如GUI渲染引擎一直持續不斷的構建DOM樹、規則樹、呈現樹以及後續的佈局和繪製。
那麼我們JS操作樣式和DOM的邏輯顯然不會在頁面上生效。
五.重排和重繪
在最後呢,我們在補充一下關於頁面渲染過程中會出現的重排和重繪現象。
先來簡單瞭解一下這兩個概念。
重繪(repaint):介面中有部分元素的外觀發生了變化,
比如:背景顏色,字型顏色,那麼瀏覽器需要將該元素的新屬性重新顯示到瀏覽器上,這個叫做重繪。
重排(reflow):重排也叫重構或者回流。
它指的是介面中有部分元素的尺寸,佈局發生了變化,那麼就會導致發生變化的這部分呈現樹重新構建,這叫做瀏覽器的重排。
新的呈現樹構建完成後,瀏覽器需要重新佈局、繪製這部分發生改變的呈現樹到瀏覽器螢幕上(重排後需要進行重繪)。
前面我們說過,呈現樹是基於DOM樹和CSS規則樹的,在這裡瀏覽器會有一個規則叫:CSS阻塞呈現樹。
因為假如我們在DOM樹和CSS規則樹構建的同時去構建呈現樹,那可想而知在後續解析CSS樣式時會發生很多次的重排和重繪。
關於重排和重繪就簡單的瞭解到這裡,這兩個現象是不可避免發生的,也不可避免的會造成頁面效能上從損失。
因此後續會有文章詳細解釋瀏覽器的重排和重繪,並且給出優化建議。
作者:小土豆biubiubiu
部落格園:www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d
簡書:https://www.jianshu.com/u/cb1c3884e6d5
微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)
碼字不易,點贊鼓勵喲~
&n