瀏覽器的工作原理是非常重要的基礎知識,而且內容非常多,有一篇文章How browsers work非常著名,其內容也十分詳實,有中文版本前端必讀:瀏覽器內部工作原理。在此基礎上,我將一些要點用我自己能理解的話總結一遍,一是梳理一遍整個的過程,另外是方便今后的復習。
總論
瀏覽器的主要構成
瀏覽器的主要構成是用戶界面、瀏覽器引擎、渲染引擎、網絡、UI后端、JS解釋器、數據存儲。在chorme中,每個標簽頁都是獨立的進程(意味著單個標簽頁的崩潰不會影響到其他標簽頁)
Paste_Image.png
渲染引擎
渲染引擎
面試常見的一個問題是瀏覽器的內核有哪些,瀏覽器的內核包括渲染引擎和JS引擎,但隨著JS引擎越來越獨立,內核就傾向只指代渲染引擎了。
瀏覽器內核 | 常見瀏覽器 |
---|---|
Trident內核 | IE、傲游、世界之窗瀏覽器、Avant、騰訊TT、360、Netscape 8、NetCaptor、Sleipnir、GOSURF、GreenBrowser和KKman等 |
Gecko內核 | Firefox、Netscape6至9 |
WebKit內核 | Safari、chrome、Opera |
EdgeHTML內核 | Edge |
渲染的主流程
解析html以構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹
Paste_Image.png
- 渲染引擎解析html,構建“DOM tree”
- CSS文件及style標簽中的樣式信息以及html中的可見性指令構建CSSOM
- DOM tree和CSSOM一起構建Render樹
- Render樹是一些含有各種屬性的矩形,根據某種規則顯示在屏幕上。
- 構建了Render樹后,會布局Render樹,這一過程確定每個節點的坐標
- 布局之后是繪制,遍歷Render樹,并使用UI后端層繪制
-
webkit主流程
Paste_Image.png -
Geoko主流程
Paste_Image.png - Geoko中的Frame樹就是Render樹,Reflow就是Layout,僅是名字不同
- Geoko在解析HTMl和構建DOM樹之間有一層Content Sink(內容接收器),用于生成DOM元素,這是webkit所沒有的、
解析與DOM樹構建
幾個概念:
- 解析:就是將一個文檔的結構轉換為代碼可以理解和使用的結構(通常是文檔結構的節點樹),稱為解析樹或語法樹。
- 文法:解析要基于文法,文法由詞匯和語法規則組成,此處的文法是上下文無關文法(“DTD”不是上下文無關,因此后面介紹的解析方法不適合HTML,但可用于CSS和JS)。
解析的過程(由淺入深)
- 解析的兩個子過程——語法分析及詞法分析
-
詞法分析:將輸入分解為符號。
- 符號:是語言的詞匯表——基本有效單元的集合。
- 詞法分析器將輸入分解為合法的符號
-
語法分析:指對語言應用語法規則。
- 解析器:根據語言的語法規則分析文檔結構,從而構建解析樹
-
詞法分析:將輸入分解為符號。
- 解析的過程進一步可視為四步:源文檔 -> 詞法分析 -> 語法分析 -> 解析樹
- 解析的更細致的迭代過程是:
- 詞法分析器得到符號,傳給解析器
- 解析器用符號匹配語法規則
* 若匹配上規則,則符號對應的節點將被添加到解析樹上 * 若沒有匹配上規則,解析器將在內部保存該符號,然后從詞法分析器取下一個符號,再次匹配規則 * 若在之后能使內部符號匹配上規則,則符號對應的節點將被添加到解析樹上 * 若到最后都沒有匹配上規則,解析器將拋出一個異常,這意味著文檔無效或是包含語法錯誤。
- 最終得到解析樹或異常
- 解析一般在轉換(將輸入文檔轉換為另一種格式)中使用,因此,解析樹可能不是最終結果,比如編譯。其過程為:源碼 -> 解析 -> 解析樹 -> 轉換 -> 機器碼
解析器類型
- 自頂向下解析,查看語法的最高層結構并試著匹配其中一個
- 自底向上解析,從輸入開始,逐步將其轉換為語法規則,從底層規則開始直到匹配高層規則
- 前文有說過,之前提到的都是上下文無關文法,但HTML的格式定義--“DTD”不是上下文無關,所以傳統解析方式(自頂向下或自底向上)都不適用于HTML(可用于CSS和JS)
DOM
- 輸出的樹,也就是解析樹,是由DOM元素節點(包括文本節點和屬性節點)組成的。
- 樹的根是“document”對象
- DOM和標簽基本是一一對應的關系
HTML解析
- HTML解析包括兩個階段——符號化(tokeniser)及構建樹(tree construction)(和之前的具體算法不一樣,但幾個過程是類似的)
- 符號化(可和傳統的詞法分析類比):是詞法分析的過程,符號識別器將輸入解析為符號(html的符號包括開始標簽、結束標簽、屬性名及屬性值)。將其傳遞給樹構建器。
- 構建樹(可和傳統的語法分析類比):樹構造器處理傳來的符號,根據規范每個符號會創建對應的Dom元素,這些元素除了會被添加到Dom樹上,還將被添加到開放元素堆棧中。這個堆棧用來糾正嵌套的未匹配和未閉合標簽(HTML“寬容”的原因)。
-
HTML解析流程
Paste_Image.png
解析結束時的處理
- 在此階段,瀏覽器會將文檔標注為交互狀態(interactive)
- 開始解析那些處于“deferred”模式的腳本,也就是那些應在文檔解析完成后才執行的腳本
- 然后,文檔狀態將設置為“完成”(complete),一個“加載”(load)事件將隨之觸發
CSS解析
- css屬于上下文無關文法,可以用前面所描述的解析器(自頂向下或自底向上)來解析
- Webkit的CSS解析器將每個css文件解析為樣式表對象,每個對象包含css規則,css規則對象包含選擇器和聲明對象,以及其他一些符合css語法的對象
處理腳本及樣式表的順序
腳本
- 解析到一個script標簽時立即解析執行腳本,并阻塞文檔的解析直到腳本執行完。
- 如果腳本是外引的,則網絡必須先請求到這個資源——這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。
- 開發者可以將腳本標識為defer,以使其不阻塞文檔解析,并在文檔解析結束后執行(
<script defer src="script.js"></script>
)。Html5增加了標記腳本為異步的選項,以使腳本的解析執行使用另一個線程。(<script async src="script.js"></script>
)
預解析
- 當執行腳本時,另一個線程解析剩下的文檔,并加載后面需要通過網絡加載的資源。這種方式可以使資源并行加載從而使整體速度更快
- 需要注意的是,預解析并不改變Dom樹,它將這個工作留給主解析過程,自己只解析外部資源的引用,比如外部腳本、樣式表及圖片
樣式表
- 因為執行腳本可能會請求樣式信息,如果此時樣式還沒有被加載和解析完成,那么腳本可能出錯。所以樣式表會阻塞腳本執行(不會阻塞外部腳本的加載)
- Firefox在存在樣式表還在加載和解析時阻塞所有的腳本
- Chrome只在當腳本試圖訪問某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本
渲染樹的構建
- DOM樹構建完后,開始構建渲染樹。Firefox將渲染樹中的元素稱為frames,WebKit則用renderer或渲染對象來描述這些元素。
- 每個渲染對象用一個和該節點的css盒模型相對應的矩形區域來表示,包含諸如寬、高和位置之類的幾何信息
渲染樹和Dom樹的關系
- 不可見的Dom元素(比如
<head>
)不會被插入渲染樹,display屬性為none的元素也不會在渲染樹中出現 - 當文本因為寬度不夠而折行時,新行將作為額外的渲染元素被添加
- 一個行內元素只能僅包含行內元素或僅包含塊狀元素,在存在混合內容時,將會創建匿名的塊狀渲染對象包裹住行內元素。
創建樹的流程
- Firefox用一個監聽器監聽DOM,Frame Constructor計算樣式并創建Frame
- Webkit中每個Dom節點有一個attach方法,調用新節點的attach方法將節點插入到Dom樹中
樣式計算
- 創建渲染樹需要計算出每個渲染對象的可視屬性,這可以通過計算每個元素的樣式屬性得到
- 樣式包括各種來源的樣式表,行內樣式元素及html中的可視化屬性(例如bgcolor),可視化屬性轉化為css樣式屬性。
樣式表的級聯順序
具有同等級別的聲明將根據specifity以及它們被定義時的順序進行排序。
- 瀏覽器聲明
- 用戶聲明
- 作者的一般聲明
- 作者的important聲明
- 用戶important聲明
Specifity
- 如果聲明來自style屬性,而不是一個選擇器的規則,則計1,否則計0(=a)
- 計算選擇器中id屬性的數量(=b)
- 計算選擇器中class及偽類的數量(=c)
- 計算選擇器中元素名及偽元素的數量(=d)
連接a-b-c-d四個數量將得到specifity。四級(a、b、c、d)之間并不是簡單的相加關系。同一級(例如:a對a)的才具有可比關系
布局
當渲染對象被創建并添加到樹中,它們并沒有位置和大小,計算這些值的過程稱為layout或reflow。
繪制
- 遍歷渲染樹并調用渲染對象的paint方法將它們的內容顯示在屏幕上,繪制使用UI基礎組件
- 一個塊渲染對象的堆棧順序是:
1. 背景色
2. 背景圖
3. border
4. children
5. outline
渲染引擎的線程
- 渲染引擎是單線程的,除了網絡操作以外,幾乎所有的事情都在單一的線程中處理,在Firefox和Safari中,這是瀏覽器的主線程,Chrome中這是tab的主線程。
- 網絡操作由幾個并行線程執行,并行連接的個數是受限的(通常是2-6個)。
事件循環
瀏覽器主線程是一個事件循環,它被設計為無限循環以保持執行過程的可用,等待事件(例如layout和paint事件)并執行它們