靈魂拷問第4篇:說一說從輸入URL到頁面呈現發生了什麼?——解析算法篇

完成了網絡請求和響應,若是響應頭中Content-Type的值是text/html,那麼接下來就是瀏覽器的解析渲染工做了。html

首先來介紹解析部分,主要分爲如下幾個步驟:前端

  • 構建 DOM
  • 樣式計算
  • 生成佈局樹(Layout Tree)

構建 DOM 樹

因爲瀏覽器沒法直接理解HTML字符串,所以將這一系列的字節流轉換爲一種有意義而且方便操做的數據結構,這種數據結構就是DOM樹DOM樹本質上是一個以document爲根節點的多叉樹。web

那經過什麼樣的方式來進行解析呢?算法

HTML文法的本質

首先,咱們應該清楚把握一點: HTML 的文法並非上下文無關文法chrome

這裏,有必要討論一下什麼是上下文無關文法編程

在計算機科學的編譯原理學科中,有很是明確的定義:瀏覽器

若一個形式文法G = (N, Σ, P, S) 的產生式規則都取以下的形式:V->w,則叫上下文無關語法。其中 V∈N ,w∈(N∪Σ)* 。bash

其中把 G = (N, Σ, P, S) 中各個參量的意義解釋一下:網絡

  1. N 是非終結符(顧名思義,就是說最後一個符號不是它, 下面同理)集合。
  2. Σ 是終結符集合。
  3. P 是開始符,它必須屬於 N ,也就是非終結符。
  4. S 就是不一樣的產生式的集合。如 S -> aSb 等等。

通俗一點講,上下文無關的文法就是說這個文法中全部產生式的左邊都是一個非終結符。前端工程師

看到這裏,若是還有一點懵圈,我舉個例子你就明白了。

好比:

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 規範詳細地介紹瞭解析算法。這個算法分爲兩個階段:

  1. 標記化。
  2. 建樹。

對應的兩個過程就是詞法分析語法分析

標記化算法

這個算法輸入爲HTML文本,輸出爲HTML標記,也成爲標記生成器。其中運用有限自動狀態機來完成。即在噹噹前狀態下,接收一個或多個字符,就會更新到下一個狀態。

<html> <body> Hello sanyuan </body> </html> 複製代碼

經過一個簡單的例子來演示一下標記化的過程。

遇到<, 狀態爲標記打開

接收[a-z]的字符,會進入標記名稱狀態

這個狀態一直保持,直到遇到>,表示標記名稱記錄完成,這時候變爲數據狀態

接下來遇到body標籤作一樣的處理。

這個時候htmlbody的標記都記錄好了。

如今來到<body>中的>,進入數據狀態,以後保持這樣狀態接收後面的字符hello sanyuan

接着接收 </body> 中的<,回到標記打開, 接收下一個/後,這時候會建立一個end tag的token。

隨後進入標記名稱狀態, 遇到>回到數據狀態

接着以一樣的樣式處理 </body>。

建樹算法

以前提到過,DOM 樹是一個以document爲根節點的多叉樹。所以解析器首先會建立一個document對象。標記生成器會把每一個標記的信息發送給建樹器建樹器接收到相應的標記時,會建立對應的 DOM 對象。建立這個DOM對象後會作兩件事情:

  1. DOM對象加入 DOM 樹中。
  2. 將對應標記壓入存放開放(與閉合標籤意思對應)元素的棧中。

仍是拿下面這個例子說:

<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 中一些經典的容錯示例,發現有其餘的也歡迎來補充。

  1. 使用</br>而不是<br>
if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; } 複製代碼

所有換爲<br>的形式。

  1. 表格離散
<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> 複製代碼
  1. 表單元素嵌套

這時候直接忽略裏面的form

樣式計算

關於CSS樣式,它的來源通常是三種:

  1. link標籤引用
  2. style標籤中的樣式
  3. 元素的內嵌style屬性

格式化樣式表

首先,瀏覽器是沒法直接識別 CSS 樣式文本的,所以渲染引擎接收到 CSS 文本以後第一件事情就是將其轉化爲一個結構化的對象,即styleSheets。

這個格式化的過程過於複雜,並且對於不一樣的瀏覽器會有不一樣的優化策略,這裏就不展開了。

在瀏覽器控制檯可以經過document.styleSheets來查看這個最終的結構。固然,這個結構包含了以上三種CSS來源,爲後面的樣式操做提供了基礎。

標準化樣式屬性

有一些 CSS 樣式的數值並不容易被渲染引擎所理解,所以須要在計算樣式以前將它們標準化,如em->px,red->#ff0000,bold->700等等。

計算每一個節點的具體樣式

樣式已經被格式化標準化,接下來就能夠計算每一個節點的具體樣式信息了。

其實計算的方式也並不複雜,主要就是兩個規則: 繼承層疊

每一個子節點都會默認繼承父節點的樣式屬性,若是父節點中沒有找到,就會採用瀏覽器默認樣式,也叫UserAgent樣式。這就是繼承規則,很是容易理解。

而後是層疊規則,CSS 最大的特色在於它的層疊性,也就是最終的樣式取決於各個屬性共同做用的效果,甚至有不少詭異的層疊現象,看過《CSS世界》的同窗應該對此深有體會,具體的層疊規則屬於深刻 CSS 語言的範疇,這裏就不過多介紹了。

不過值得注意的是,在計算完樣式以後,全部的樣式值會被掛在到window.getComputedStyle當中,也就是能夠經過JS來獲取計算後的樣式,很是方便。

生成佈局樹

如今已經生成了DOM樹DOM樣式,接下來要作的就是經過瀏覽器的佈局系統肯定元素的位置,也就是要生成一棵佈局樹(Layout Tree)。

佈局樹生成的大體工做以下:

  1. 遍歷生成的 DOM 樹節點,並把他們添加到佈局樹中
  2. 計算佈局樹節點的座標位置。

值得注意的是,這棵佈局樹值包含可見元素,對於 head標籤和設置了display: none的元素,將不會被放入其中。

有人說首先會生成Render Tree,也就是渲染樹,其實這仍是 16 年以前的事情,如今 Chrome 團隊已經作了大量的重構,已經沒有生成Render Tree的過程了。而佈局樹的信息已經很是完善,徹底擁有Render Tree的功能。

之因此不講佈局的細節,是由於它過於複雜,一一介紹會顯得文章過於臃腫,不過大部分狀況下咱們只須要知道它所作的工做是什麼便可,若是想深刻其中的原理,知道它是如何來作的,我強烈推薦你去讀一讀人人FED團隊的文章從Chrome源碼看瀏覽器如何layout佈局

總結

梳理一下這一節的主要脈絡:

 

相關文章
相關標籤/搜索