完成了網絡請求和響應,若是響應頭中Content-Type
的值是text/html
,那麼接下來就是瀏覽器的解析
和渲染
工做了。html
首先來介紹解析部分,主要分爲如下幾個步驟:前端
DOM
樹樣式
計算佈局樹
(Layout Tree
)因爲瀏覽器沒法直接理解HTML字符串
,所以將這一系列的字節流轉換爲一種有意義而且方便操做的數據結構,這種數據結構就是DOM樹
。DOM樹
本質上是一個以document
爲根節點的多叉樹。web
那經過什麼樣的方式來進行解析呢?算法
首先,咱們應該清楚把握一點: HTML 的文法並非上下文無關文法
。chrome
這裏,有必要討論一下什麼是上下文無關文法
。編程
在計算機科學的編譯原理學科中,有很是明確的定義:瀏覽器
若一個形式文法G = (N, Σ, P, S) 的產生式規則都取以下的形式:V->w,則叫上下文無關語法。其中 V∈N ,w∈(N∪Σ)* 。bash
其中把 G = (N, Σ, P, S) 中各個參量的意義解釋一下:網絡
通俗一點講,上下文無關的文法
就是說這個文法中全部產生式的左邊都是一個非終結符。前端工程師
看到這裏,若是還有一點懵圈,我舉個例子你就明白了。
好比:
A -> B
複製代碼
這個文法中,每一個產生式左邊都會有一個非終結符,這就是上下文無關的文法
。在這種狀況下,xBy
必定是能夠規約出xAy
的。
咱們下面看看看一個反例:
aA -> B
Aa -> B
複製代碼
這種狀況就是否是上下文無關的文法
,當遇到B
的時候,咱們不知道到底能不能規約出A
,取決於左邊或者右邊是否有a
存在,也就是說和上下文有關。
關於它爲何是非上下文無關文法
,首先須要讓你們注意的是,規範的 HTML 語法,是符合上下文無關文法
的,可以體現它非上下文無關
的是不標準的語法。在此我僅舉一個反例便可證實。
好比解析器掃描到form
標籤的時候,上下文無關文法的處理方式是直接建立對應 form 的 DOM 對象,而真實的 HTML5 場景中卻不是這樣,解析器會查看 form
的上下文,若是這個 form
標籤的父標籤也是 form
, 那麼直接跳過當前的 form
標籤,不然才建立 DOM 對象。
常規的編程語言都是上下文無關的,而HTML卻相反,也正是它非上下文無關的特性,決定了HTML Parser
並不能使用常規編程語言的解析器來完成,須要另闢蹊徑。
HTML5 規範詳細地介紹瞭解析算法。這個算法分爲兩個階段:
對應的兩個過程就是詞法分析和語法分析。
這個算法輸入爲HTML文本
,輸出爲HTML標記
,也成爲標記生成器。其中運用有限自動狀態機來完成。即在噹噹前狀態下,接收一個或多個字符,就會更新到下一個狀態。
<html> <body> Hello sanyuan </body> </html> 複製代碼
經過一個簡單的例子來演示一下標記化
的過程。
遇到<
, 狀態爲標記打開。
接收[a-z]
的字符,會進入標記名稱狀態。
這個狀態一直保持,直到遇到>
,表示標記名稱記錄完成,這時候變爲數據狀態。
接下來遇到body
標籤作一樣的處理。
這個時候html
和body
的標記都記錄好了。
如今來到<body>中的>,進入數據狀態,以後保持這樣狀態接收後面的字符hello sanyuan。
接着接收 </body> 中的<
,回到標記打開, 接收下一個/
後,這時候會建立一個end tag
的token。
隨後進入標記名稱狀態, 遇到>
回到數據狀態。
接着以一樣的樣式處理 </body>。
以前提到過,DOM 樹是一個以document
爲根節點的多叉樹。所以解析器首先會建立一個document
對象。標記生成器會把每一個標記的信息發送給建樹器。建樹器接收到相應的標記時,會建立對應的 DOM 對象。建立這個DOM對象
後會作兩件事情:
DOM對象
加入 DOM 樹中。閉合標籤
意思對應)元素的棧中。仍是拿下面這個例子說:
<html> <body> Hello sanyuan </body> </html> 複製代碼
首先,狀態爲初始化狀態。
接收到標記生成器傳來的html
標籤,這時候狀態變爲before html狀態。同時建立一個HTMLHtmlElement
的 DOM 元素, 將其加到document
根對象上,並進行壓棧操做。
接着狀態自動變爲before head, 此時從標記生成器那邊傳來body
,表示並無head
, 這時候建樹器會自動建立一個HTMLHeadElement並將其加入到DOM樹
中。
如今進入到in head狀態, 而後直接跳到after head。
如今標記生成器傳來了body
標記,建立HTMLBodyElement, 插入到DOM
樹中,同時壓入開放標記棧。
接着狀態變爲in body,而後來接收後面一系列的字符: Hello sanyuan。接收到第一個字符的時候,會建立一個Text節點並把字符插入其中,而後把Text節點插入到 DOM 樹中body元素
的下面。隨着不斷接收後面的字符,這些字符會附在Text節點上。
如今,標記生成器傳過來一個body
的結束標記,進入到after body狀態。
標記生成器最後傳過來一個html
的結束標記, 進入到after after body的狀態,表示解析過程到此結束。
講到HTML5
規範,就不得不說它強大的寬容策略, 容錯能力很是強,雖然你們褒貶不一,不過我想做爲一名資深的前端工程師,有必要知道HTML Parser
在容錯方面作了哪些事情。
接下來是 WebKit 中一些經典的容錯示例,發現有其餘的也歡迎來補充。
if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; } 複製代碼
所有換爲<br>的形式。
<table> <table> <tr><td>inner table</td></tr> </table> <tr><td>outer table</td></tr> </table> 複製代碼
WebKit
會自動轉換爲:
<table> <tr><td>outer table</td></tr> </table> <table> <tr><td>inner table</td></tr> </table> 複製代碼
這時候直接忽略裏面的form
。
關於CSS樣式,它的來源通常是三種:
首先,瀏覽器是沒法直接識別 CSS 樣式文本的,所以渲染引擎接收到 CSS 文本以後第一件事情就是將其轉化爲一個結構化的對象,即styleSheets。
這個格式化的過程過於複雜,並且對於不一樣的瀏覽器會有不一樣的優化策略,這裏就不展開了。
在瀏覽器控制檯可以經過document.styleSheets
來查看這個最終的結構。固然,這個結構包含了以上三種CSS來源,爲後面的樣式操做提供了基礎。
有一些 CSS 樣式的數值並不容易被渲染引擎所理解,所以須要在計算樣式以前將它們標準化,如em
->px
,red
->#ff0000
,bold
->700
等等。
樣式已經被格式化
和標準化
,接下來就能夠計算每一個節點的具體樣式信息了。
其實計算的方式也並不複雜,主要就是兩個規則: 繼承和層疊。
每一個子節點都會默認繼承父節點的樣式屬性,若是父節點中沒有找到,就會採用瀏覽器默認樣式,也叫UserAgent樣式
。這就是繼承規則,很是容易理解。
而後是層疊規則,CSS 最大的特色在於它的層疊性,也就是最終的樣式取決於各個屬性共同做用的效果,甚至有不少詭異的層疊現象,看過《CSS世界》的同窗應該對此深有體會,具體的層疊規則屬於深刻 CSS 語言的範疇,這裏就不過多介紹了。
不過值得注意的是,在計算完樣式以後,全部的樣式值會被掛在到window.getComputedStyle
當中,也就是能夠經過JS來獲取計算後的樣式,很是方便。
如今已經生成了DOM樹
和DOM樣式
,接下來要作的就是經過瀏覽器的佈局系統肯定元素的位置
,也就是要生成一棵佈局樹
(Layout Tree)。
佈局樹生成的大體工做以下:
佈局樹中
。值得注意的是,這棵佈局樹值包含可見元素,對於 head
標籤和設置了display: none
的元素,將不會被放入其中。
有人說首先會生成Render Tree
,也就是渲染樹,其實這仍是 16 年以前的事情,如今 Chrome 團隊已經作了大量的重構,已經沒有生成Render Tree
的過程了。而佈局樹的信息已經很是完善,徹底擁有Render Tree
的功能。
之因此不講佈局的細節,是由於它過於複雜,一一介紹會顯得文章過於臃腫,不過大部分狀況下咱們只須要知道它所作的工做是什麼便可,若是想深刻其中的原理,知道它是如何來作的,我強烈推薦你去讀一讀人人FED團隊的文章從Chrome源碼看瀏覽器如何layout佈局。
梳理一下這一節的主要脈絡: