4-解析-HTML 解析器
因為 HTML 語言在語法層面並有那麼嚴格的語法規則,導致常規的解析器並不能解析HTML文件,對應的解決方案讓瀏覽器廠商自定義 HTML 解析器。那麼,讓我們一起梳理一下 HTML 解析器到底是什麼吧~
輸入(語法)
因為 HTML 語法是由 W3C 組織建立的規範中進行定義的,而且語法格式是由 DTD (Document Type Definition)定義的,該格式中定義了語言中允許的元素、屬性和層次結構,適用一切的 SGML (Standard Gerneralized Markup Languge)族的語言。為了在發展的程式中向後相容老版本的內容,DTD 存在兩種模式,嚴格模式完全遵守 HTML 規範,其他模式支援老的瀏覽器使用的編輯。
解析演算法
因為 HTML 文件語法特性(包容性),以及在解析過程中存在指令碼會改變 HTML 文件(如: document.write),導致無法使用自上而下或是自下而上的解析器進行解析。
解析過程前半段是詞法分析,也就是標記化(tokenization),整體演算法的核心就是狀態機的改變(就是解析過程中有一個標識當前狀態應是解析到哪一個階段了)。
同時構建 DOM 樹,也就是樹構建(tree construction)過程,該過程就是我們在「解析-理論剖析」講述的一樣,將對應的標記去擊中語法,然後新增到 DOM 樹上。此過程中也有一個狀態機去維護對應的階段。
最後 DOM 樹是 HTML 文件的對映關係和存留 HTML 元素對外的介面(如: 對JS),每一個節點是由 DOM 元素和節點屬性組成。看一個例子:
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
複製程式碼
解析完,進入互動階段開始解析處於 'deferred mode' (that should be executed after the document is parsed)的指令碼,執行完這些指令碼後,文件狀態為 complete,觸發 load 事件。
因為解析器是瀏覽器廠商的自定義,而 HTML語法特性比較特殊,所以解析器要有相關的容錯機制,而這機制並不是 HTML 規範中強制規定,而是瀏覽器發展過程中的產品(友商之間互抄好的地方唄),但是後期的 HTML 5 規範中有部分容錯機制的要求(webkit 的 HTML 解析器就有這樣的註釋)。
上述只是描述到 HTML 文件的解析,那指令碼和樣式的解析順序呢?
因為 web 的模型式同步的原因,如果遇見內部 <script>
標籤,就會中斷 HTML 解析,開始執行指令碼,直到指令碼執行完畢,而遇到外部的指令碼,解析同樣中斷直到請求指令碼回來。這些解析模式在 HTML 4 5規範中有所描述。
畢竟突然中斷 HTML 解析還是會影響頁面展示的時間,那樣我們需要規避不必要的因指令碼而中斷解析,那就是給 <script>
新增 defer 屬性,這是 HTML 5中給指令碼標記為非同步的標識,這樣指令碼通過不同的執行緒解析和執行。
當有指令碼在執行的過程中,會觸發「預解析」,此時是其他執行緒繼續解析文件,找到需要請求載入的資源,載入這部分資源(並行載入,提高整體速度)。預解析只解析外部檔案(外部指令碼、樣式或圖片),此過程不修改 DOM 樹。
樣式方面,因為解析樣式並不會影響 DOM 樹,所以不需要中斷文件解析。但同時存在一個問題,當指令碼獲取樣式資訊時,但此時樣式並沒有載入就會報錯。對應的解決手段就是阻塞指令碼,但不用瀏覽器阻塞的階段不同,firefox是當樣式載入或解析的時候,會阻塞所有的指令碼;而 webkit 是當指令碼去訪問那些確定會被為載入樣式影響到的屬性時,阻塞指令碼。
以上是剛剛完成了 DOM 樹的構建,那我們馬上進入後續階段咯~