[譯]從輸入URL到頁面呈現的超詳細過程——第二步:Tags轉化成DOM的過程

wangds發表於2019-02-25

原文連結:Tags to DOM
原文作者:Travis Leithead
譯者:wangds

這是一個系列文章,分為四個部分介紹了從輸入URL到頁面呈現的詳細過程

  • 客戶端從伺服器獲取資源(Server to Client)點選檢視
  • 標籤轉化成DOM的過程(tags to DOM)
  • CSS解析的過程(braces to pixels)
  • 編譯執行javascript的過程(var to JIT)

本篇翻譯的是第二部分:Tags to DOM

在上一篇文章中(客戶端從伺服器獲取資源),我們談論了資源是如何從伺服器到達客戶端的,同時也闡述了一些關於快取、同源的概念。本篇文章將聊一聊HTML資源是如何轉化成DOM tree的。

解析的過程(Parsing)

當瀏覽器獲得了資源以後要進行的第一步工作就是解析,整個解析的過程可以拆分成如下四個步驟:

  1. 解碼(encoding)
  2. 預解析(pre-parsing)
  3. 符號化(Tokenization)
  4. 構建樹(tree construction)

1.解碼(encoding)

客戶端接收的內容可以是HTML文字、影象等等任何格式的資料。在從伺服器到客戶端展示的過程中,資料經過了如下的過程:

  1. 伺服器將資料轉換成bit
  2. bit進行資料傳輸
  3. 客戶端將bit轉化成資料

所以客戶端必須知道資料轉化成bit的格式,以便客戶端將bit反轉成正確的資料。

我們以HTML舉例來說,HTML的文字格式有很多種,所以伺服器在返回資料的時候在響應頭中使用Content-Type告知瀏覽器文字的內容型別,同時在文字檔案頭部使用Byte Order Mark告知瀏覽器編碼格式。如果瀏覽器仍然判斷不了解碼方式,瀏覽器還會自行匹配一種解碼格式來處理資料。有時候,解碼格式也會寫在<meta>標籤中。這就導致會出現一種奇葩的現象,瀏覽器先是以自己的方式去解碼文字,然後遇到了標明解碼方式的<meta>標籤。這時候瀏覽器就會重新解析按照<meta>標籤進行解析。

我們現在經常在HTML中使用的檔案格式是UTF-8,那是因為UTF-8能較完整的支援Unicode字元範圍,同時與CSSJavaScript中常見的節字元具有良好的ASCII相容性。一般瀏覽器預設的解碼格式也是 UTF-8。當解碼出錯的時候,我們會看到螢幕上全部都是亂碼字元。

2.預解析(Pre-parsing/scanning)

完成了解碼以後,瀏覽器會開啟一個預解析的過程,“偷偷獲取”後面需要載入的資源,減少處理的時間。預解析的過程不是一個完整的解析過程。舉例來說,預解析不會處理HTML內元素之間巢狀的父子層級。但是預解析還是能識別一些特殊的HTML標籤、元素屬性值以及URL。假如我們的頁面中有個img(如下),預解析就會注意src屬性值,並將獲取這個圖片的請求加到請求佇列中,儘早的將圖片獲取到本地以便顯示。我們也可以通過prefetchpreload兩種屬性將需要儘早載入的資源告知於瀏覽器。

<img src="https://somewhere.example.com/​images/​dog.png" alt="">
複製程式碼

3.符號化(TOKENIZATION)

符號化是將輸入解析成為符號,像開始標籤(<)、結束標籤(>)、註釋(//)、文字內容等等。然後這些符號將被送到解析的下一個步驟。完成符號化工作的機器叫做符號識別器( tokenizer),符號識別器是一種狀態機。 我們以<video controls>的標籤為例:

  • 狀態機初始狀態是Data State,
  • 當遇到<的時候,狀態變成Tag open state
  • 當解析到字母v的時候,狀態變成Tag name state
  • 當解析到字母c的時候,狀態變成in attribute name state
  • 解析完字母s的時候,狀態變成after attribute name state
  • 解析完>的時候,狀態變成Data state

從解析<video controls>標籤我們就可以知道,整個頁面會碰到很多標籤,符號識別器就會不斷的重複狀態變化。(下圖中的箭頭就是狀態機處於的某個位置)

avatar

HTML標準目前為符號識別器定義了80個獨立的狀態。在解析的過程中,符號識別器可以將任何的文字轉化成HTML文件(即便文字內容不是有效的HTML)。這種彈性的機制使得HTML更加的易於開發,但是也可能導致因為寫錯標籤出現特殊的bug。

對於那些更喜歡使用‘非黑即白’驗證規則的人,瀏覽器內建了一種替代解析機制,可將任何解析錯誤視為災難性的故障(出現錯誤就會導致無法呈現內容)。 這種解析模式使用XML規則來處理HTML,我們可以通過設定MIME型別的值為application / xhtml + xml開啟這種解析模式。

瀏覽器可以同時處理預解析和標記化兩個過程(也就是說這是兩個執行緒)。

4. 構建樹(tree construction)

在上一步符號化以後,解析器獲得這些符號標記,然後以合適的方法建立DOM物件並將這些符號插入到DOM物件中。DOM物件的資料結構是樹狀的,所以這個過程稱為構造樹(tree construction)。順便說一句,在IE的歷史中,大部分時間裡沒有使用樹結構。

avatar

另外現代瀏覽器為了相容舊版本HTML,解析的過程還是十分複雜的。例如瀏覽器可以自動閉合標籤。

<p>sincerely<p>The authors</p> //HTML的樣子

<p>sincerely</p><p>The authors</p>//瀏覽器解析出來的樣子
複製程式碼

我們可以使用JavaScript去任意修改DOM。假如我們用JavaScript進行無意義的操作(例如在video元素內插入一個table單元格),渲染引擎可以甄別出這些操作不合規,也不進行渲染(但是這個過程渲染引擎也進行工作只是不展示)。

因為JavaScript可以在DOM中新增內容。所以遇到JavaScript檔案的時候,DOM將停止解析;如果JavaScript檔案內呼叫了document.writeAPI,解析器將重新開始解析過程。

事件(Events)

當整個解析的過程完成以後,瀏覽器會通過DOMContentLoaded事件來通知DOM解析完成。事件相當於一套廣播系統用來通知和監聽瀏覽器。除了DOMContentLoaded事件,還有load事件(表示所有資源已經載入完成,包括圖片、視訊、CSS等等)、unload事件表示介面即將關閉、滑鼠事件鍵盤事件等等。

瀏覽器在DOM物件中還會建立一個事件物件,該物件包含大量有用的資訊(如螢幕中的座標、按下的按鍵等等)。當JavaScript觸發事件的時候,就會同時產生事件物件。

在目標元素上觸發事件的時候,需要從DOM樹的根元素開始向子元素查詢,這個過程俗稱事件捕捉階段。到達目標元素以後,還要逐級向上返回到根元素上,這個過程俗稱事件冒泡階段。

avatar

事件是可以進行關閉的,我們可以在事件捕獲或者傳播的過程中選擇關閉,例如在form的提交過程中判斷字串為空則關閉提交。

DOM

解析器解析出來的元素可以包含一些基本的互動功能(<video>能播放視訊、<button>按鈕等),但是這些基本的互動功能還不能滿足我們的需求,我們需要使用cssJavaScript將頁面展示的更加豐富多彩,而DOM就是HTML元素與其他資源互動的橋樑。

元素的介面

在解析器將元素放入DOM樹之前,解析器會根據不同元素的名稱賦予元素不同的介面功能。

下面是一些通用的功能:

  • 可以訪問元素內的子元素集合
  • 可以搜尋子元素的屬性和方法
  • 最重要的是,可以不通過解析器新增新的元素到DOM

<table>元素具有查詢刪除表內所有單元格的能力;<canvas>具有描繪線條、圖形的能力。但是想要呼叫這個介面還是需要JavaScript來操作。

每當我們使用JavaScript操作DOM的時候,將會觸發瀏覽器的一些連鎖反應,這些反應是為了讓更改後的頁面更快的呈現在螢幕上。例如:

  • 用數字代表通用的元素名稱和屬性,瀏覽器用使用雜湊表進行快速識別這些數字
  • 將頻繁變更的子元素進行快取,方便子元素快速迭代
  • sub-tree的跟蹤變化降到最低,避免‘汙染’整個DOM

DOM中的HTML元素是頁面展示的基礎,DOM的能力也不僅僅侷限於上面我們提到的功能。其他諸如訪問儲存系統(瀏覽器中的快取)、裝置功能(藍芽、定位)、以及3D圖形繪製等等功能。隨著瀏覽器的不斷進步和web標準的不斷實施,DOM的功能只會越來越強大,本文只涉及了一部分而已。

結束語

以上就包含了標籤轉化成DOM的過程。下一篇文章我們將聊聊CSS解析的過程。

限於本人水平有限,文中如有錯誤感謝您指正。

相關文章