瀏覽器解析HTML文檔生成DOM樹的過程,以下是一段HTML代碼,以此為例來分析解析HTML文檔的原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="script.js"></script> <link rel="stylesheet" type="text/css" href="style.css"> <title></title> </head> <body> <h1>HelloWorld</h1> <div> <div> <p>picture:</p> <img src="example.png"/> </div> <div> <p>A paragraph of explanatory text...</p> </div> </div> </body> </html>
豌豆資源搜索網站 https://55wd.com
瀏覽器解析HTML文檔,在<head>中發現了<script>和<link>引入文件,於是向服務器請求文件,在請求和下載文件過程中將繼續向下解析HTML,當引入文件下載完成后會通知瀏覽器回頭來解析css和script文件。
如果瀏覽器在代碼中發現一個<img>標簽引用了一張圖片,向服務器發出請求。此時瀏覽器同樣不會等到圖片下載完,而是繼續渲染后面的代碼;
現在進入正題,講講自己對解析HTML文檔構建DOM樹的理解:
此過程可分為兩個主要模塊構成,即
- 標簽解析
- DOM樹構建
1. 標簽解析
這部分完成從HTML字符串中解析出標簽的功能。主要使用標記化算法。
標記化算法的輸入結果是HTML標記,使用狀態機表示。狀態機一共有4個狀態:數據狀態(Data)、標記打開狀態(Tag open)、標記名稱狀態(Tag name)、關閉標記打開狀態(Close tag open state)。
初始狀態是數據狀態。
當標記是處於數據狀態時,
1)遇到字符<時,狀態更改為“標記打開狀態”:
a. 接收一個a-z字符會創建“起始標記”,狀態更改為“標記名稱狀態”,並保持到接收>字符。此期間的字符串會形成一個新的標記名稱。接收到>標記后,將當前的新標記發送給樹構造器,狀態改回“數據狀態”
b. 接收下一個輸入字符/時,會創建關閉標記打開狀態,並更改為“標記名稱狀態”。直到接收>字符,將當前的新標記發送給樹構造器,並改回“數據狀態”。
2)遇到a-z字符時,會將每個字符創建成字符標記,並發送給樹構造器。
2. DOM樹構建
當標簽解析器解析出標簽后會發送到DOM樹構建器,我們可以認為DOM樹構建器主要有以下兩部分組成:
- DOM樹
- 一個存放標簽名的棧
用如下代碼演示生成DOM樹的過程:
<html> <body> <h1>HelloWorld</h1> <div> <div> <p>picture:</p> <img src="example.png"/> </div> <div> <p>A paragraph of explanatory text...</p> </div> </div> </body> </html> <span><span class="tag"></span></span>
首先樹構建器接收到標簽解析器發來的起始標簽名后,會加入到棧中,圖1是解析到<h1>標簽的棧中壓入的內容,共有<html><body><h1>三個標簽,此時還未向DOM樹中添加任何結點(圖中黑色實線框代表開始標簽,紅色虛線框代表結束標簽,結束標簽不會入棧)。
繼續向下解析,接收到一個</h1>結束標簽,此時查詢棧頂元素,如果和傳入的結束標簽屬於同種類型的p標簽(如圖2),則將棧頂元素彈出,向DOM樹中加入此節點,然后繼續向下解析(如圖3)。
如果遇到的是沒有封閉標簽的元素如<img/>,則直接加入DOM樹中即可,無需入棧。
依次向下解析,當棧為空,即<html>根節點也加入到DOM樹中,DOM樹構建完畢。
這里需要指出的是,當某個元素缺失結束標簽時,假如上述代碼中第一個<div>標簽缺失了</div>結束標簽,即:
<html> <body> <h1>HelloWorld</h1> <div> <div> <p>picture:</p> <img src="example.png"/> </div> <div> <p>A paragraph of explanatory text...</p> </div> </body> </html>
那么,此時的棧如圖4所示。即此時傳來的結束標簽是</body>,而棧頂元素是<div>,兩者不是同一種標簽,說明div缺少了結束標簽,這種情況也將棧頂<div>元素彈出,加入到DOM樹中。相當於給<div>補了一個</div>結束標簽。