瀏覽器將標籤轉成 DOM 的過程

瀏覽器基本的工做流程

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!css

進入主話題以前,先羅列一下瀏覽器的主要構成:html

  1. 用戶界面- 包括地址欄、後退/前進按鈕、書籤目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口以外的其餘部分
  2. 瀏覽器引擎- 用來查詢及操做渲染引擎的接口
  3. 渲染引擎- 用來顯示請求的內容,例如,若是請求內容爲html,它負責解析html及css,並將解析後的結果顯示出來
  4. 網絡- 用來完成網絡調用,例如http請求,它具備平臺無關的接口,能夠在不一樣平臺上工做
  5. UI 後端- 用來繪製相似組合選擇框及對話框等基本組件,具備不特定於某個平臺的通用接口,底層使用操做系統的用戶接口
  6. JS解釋器- 用來解釋執行JS代碼
  7. 數據存儲- 屬於持久層,瀏覽器須要在硬盤中保存相似cookie的各類數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術

clipboard.png

解析

當瀏覽器得到了資源之後要進行的第一步工做就是 HTML 解析,,它由幾個步驟組成:編碼、預解析、標記和構建樹。前端

編碼

HTTP 響應主體的有效負載能夠是從HTML文本到圖像數據的任何內容。解析器的第一項工做是找出如何轉制剛剛從服務器接收到的 bitgit

假設咱們正在處理一個HTML文檔,解碼器必須弄清楚文本文檔是如何被轉換成比特(bit)的,以便反轉這個過程。github

clipboard.png

記住,最終即便是文本也會被計算機翻譯成二進制,如上圖所示,在本例中是 ASCII 編碼—定義二進制值,如「01000100」表示字母「D」。web

對於文本存在許多可能的編碼—瀏覽器的工做是找出如何正確地解碼文本。服務器應該經過 Content-Type 提供的信息同時在文本文件頭部使用 Byte Order Mark 告知瀏覽器編碼格式。算法

若是仍然沒法肯定編碼,瀏覽器還會自行匹配一種解碼格式來處理數據。有時候,解碼格式也會寫在 <meta> 標籤中。數據庫

最壞的狀況是,瀏覽器進行了有根據的猜想,而後開始解析以後發現一個矛盾的 <meta> 標籤。在這些罕見的狀況下,解析器必須從新啓動,丟棄以前解碼的內容。瀏覽器有時必須處理舊的 web內容(使用遺留編碼),許多這樣的系統都支持這一點。canvas

咱們如今常常在 HTML中使用的文件格式是 UTF-8,那是由於 UTF-8 能較完整的支持Unicode 字符範圍,同時與 CSS、JavaScript 中常見的節字符具備良好的 ASCII 兼容性。通常瀏覽器默認的解碼格式也是 UTF-8。當解碼出錯的時候,咱們會看到屏幕上所有都是亂碼字符。segmentfault

預解析

在執行腳本時,其餘線程會解析文檔的其他部分,找出並加載須要經過網絡加載的其餘資源。經過這種方式,資源能夠在並行鏈接上加載,從而提升整體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工做交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。

預解析器不是完整的解析器,如,它不理解 HTML 中的嵌套級別或父/子關係。可是,預解析能夠識別特定的 HTML 標籤的名稱和屬性,以及 URL。例如,若是你的 HTML 內容中有一個<img src="https://somewhere.example.com/​images/​dog.png" alt=""> ,預解析將注意到src屬性,並將獲取這個圖片的請求加到請求隊列中。

請求圖片的速度越快越好,將等待它從網絡到達的時間降到最低。預解析還會注意到 HTML 中的某些顯式請求,好比 preloadprefetch 指令,並將它們加入等待隊友中進行處理。

標記化(Tokenization)

該算法的輸出結果是 HTML 標記。該算法使用狀態機來表示。每個狀態接收來自輸入信息流的一個或多個字符,並根據這些字符更新下一個狀態。當前的標記化狀態和樹結構狀態會影響進入下一狀態的決定。這意味着,即便接收的字符相同,對於下一個正確的狀態也會產生不一樣的結果,具體取決於當前的狀態。該算法至關複雜,沒法在此詳述,因此咱們經過一個簡單的示例來幫助你們理解其原理。

基本示例 - 將下面的 HTML 代碼標記化:

<html>
  <body>
    Hello world
  </body>
</html>

初始狀態是數據狀態。遇到字符 < 時,狀態更改成「標記打開狀態」。接收一個 a-z 字符會建立「起始標記」,狀態更改成「標記名稱狀態」。這個狀態會一直保持到接收 > 字符。在此期間接收的每一個字符都會附加到新的標記名稱上。在本例中,咱們建立的標記是 html 標記。

遇到 > 標記時,會發送當前的標記,狀態改回「數據狀態」。<body> 標記也會進行一樣的處理。目前 html 和 body 標記均已發出。如今咱們回到「數據狀態」。接收到 Hello world 中的 H 字符時,將建立併發送字符標記,直到接收 </body> 中的 <。咱們將爲 Hello world 中的每一個字符都發送一個字符標記。

如今咱們回到「標記打開狀態」。接收下一個輸入字符 / 時,會建立 end tag token 並改成「標記名稱狀態」。咱們會再次保持這個狀態,直到接收 >。而後將發送新的標記,並回到「數據狀態」。</html> 輸入也會進行一樣的處理。

圖片描述

構建樹(tree construction)

在建立解析器的同時,也會建立 Document 對象。在樹構建階段,以 Document 爲根節點的 DOM 樹也會不斷進行修改,向其中添加各類元素。標記生成器發送的每一個節點都會由樹構建器進行處理。規範中定義了每一個標記所對應的 DOM 元素,這些元素會在接收到相應的標記時建立。這些元素不只會添加到 DOM 樹中,還會添加到開放元素的堆棧中。此堆棧用於糾正嵌套錯誤和處理未關閉的標記。其算法也能夠用狀態機來描述。這些狀態稱爲「插入模式」

在上一步符號化之後,解析器得到這些標記,而後以合適的方法建立 DOM 對象並將這些符號插入到 DOM 對象中。DOM 對象的數據結構是樹狀的,因此這個過程稱爲構造樹(tree construction)。另外,在 IE 的歷史中,大部分時間裏沒有使用樹結構。

圖片描述

在建立解析器的同時,也會建立 Document 對象。在樹構建階段,以 Document 爲根節點的 DOM 樹也會不斷進行修改,向其中添加各類元素。標記生成器發送的每一個節點都會由樹構建器進行處理。

規範中定義了每一個標記所對應的 DOM 元素,這些元素會在接收到相應的標記時建立。這些元素不只會添加到 DOM 樹中,還會添加到開放元素的堆棧中。此堆棧用於糾正嵌套錯誤和處理未關閉的標記。其算法也能夠用狀態機來描述。這些狀態稱爲「插入模式」。

例如,考慮這個 HTML:

<p>sincerely<p>The authors</p>

這樣能夠確保結果樹中的兩個段落對象是兄弟節點,而忽略第二個打開的標籤則與一個段落對象相對。 HTML表多是解析器規則試圖確保表具備適當結構的最複雜的表。

儘管存在全部複雜的解析規則,可是一旦建立了 DOM 樹,全部試圖建立正確 HTML 結構的解析規則就再也不強制執行了。

使用 JavaScript,網頁能夠幾乎以任何方式從新排列 DOM 樹,即便它沒有意義,例如,添加表格單元格做爲 <video> 標籤的子項,渲染系統負責弄清楚如何處理任何先後不一致標籤。

HTML 解析中的另外一個複雜因素是 JavaScript 能夠在解析器執行其工做時添加更多要解析的內容。<script> 標籤包含解析器必須收集的文本,而後發送到腳本引擎進行評估。 當腳本引擎解析並評估腳本文本時,解析器會等待。若是JavaScript文件內調用了 document.writeAPI,解析器將從新開始解析過程。

事件(Events)

當解析器完成時,它經過一個名爲 DOMContentLoaded 的事件宣佈完成。事件是內置在瀏覽器中的廣播系統,JavaScript能夠偵聽和響應它。除了 DOMContentLoaded 事件,還有load 事件(表示全部資源已經加載完成,包括圖片、視頻、CSS等等)、unload 事件表示界面即將關閉、鼠標事件鍵盤事件等等。

瀏覽器在 DOM 中建立一個事件對象,並將其打包成有用的狀態信息(例如屏幕上觸摸的位置、按下的按鍵等等),當JavaScript觸發事件的時候,就會同時產生事件對象。

DOM 的樹結構經過容許在樹的任何級別監聽事件(如在樹根、樹葉或二者之間的任何地方)。在目標元素上觸發事件的時候,須要 從DOM 樹的根元素開始向子元素查找,這個過程俗稱事件捕捉階段。到達目標元素之後,還要逐級向上返回到根元素上,這個過程俗稱事件冒泡階段

圖片描述

還能夠取消一些事件,例如,若是表單沒有正確填寫,則能夠中止表單提交。(提交事件是從<form> 元素觸發的,JavaScript 偵聽器能夠檢查表單,若是字段爲空或無效,還能夠選擇取消事件。)

DOM

HTML語言提供了豐富的特性集,遠遠超出瞭解析器處理的標記。解析器構建一個結構,其中的元素包含其餘元素,以及這些元素最初具備什麼狀態(它們的屬性)。結構和狀態的組合足以提供基本渲染和一些交互(例如經過內置控件,如<textarea>,<video>,<button>等)。 可是若是不添加 CSS 和 JavaScript,網絡將很是枯燥(和靜態)。 DOM 爲 HTML 元素和與 HTML 無關的其餘對象提供了額外的功能層。

元素接口

在解析器將元素放入DOM樹以前,解析器會根據不一樣元素的名稱賦予元素不一樣的接口功能。些通用特性包括:

  • 訪問表明元素子元素的所有或子集的 HTML 集合
  • 可以查找元素的屬性、子元素和父元素
  • 重要的是,建立新元素的方法(不使用解析器),並將它們附加到樹中(或將它們從樹中分離出來)

對於像 <table> 這樣的特殊元素,該接口包含用於查找表中全部行,列和單元格的其餘特定於表的功能,以及用於從表中刪除和添加行和單元格的快捷方式。 一樣,<canvas> 接口具備繪製線條,形狀,文本和圖像的功能。 使用這些 API 須要 JavaScript 僅僅使用 HTML 標籤是不夠的。

每當咱們使用 JavaScript 操做 DOM 的時候,將會觸發瀏覽器的一些連鎖反應,這些反應是爲了讓更改後的頁面更快的渲染在屏幕上。例如:

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

其餘API

DOM中的HTML元素及其接口是瀏覽器在屏幕上顯示內容的惟一機制。CSS能夠影響佈局,但僅限於HTML元素中存在的內容。最終,若是你想在屏幕上看到內容,它必須經過做爲樹的一部分的HTML接口來完成。

  1. 訪問存儲系統(數據庫,key/value存儲,網絡緩存存儲(network cache storage));
  2. 設備(各類類型的地理定位,距離和方向傳感器,USB,MIDI,藍牙,遊戲手柄);
  3. 網絡(HTTP交換,雙向服務器套接字,實時媒體流);
  4. 圖形(2D和3D圖形基元,着色器,虛擬和加強現實);
  5. 和多線程(具備豐富消息傳遞功能的共享和專用執行環境)。

隨着主要瀏覽器引擎開發和實施新的Web標準,DOM公開的功能不斷增長。然而,DOM的這些「額外」API中的大多數都超出了本文的範圍。

總結

但願這部分對你關於 DOM 解析過程多多少少有點幫助,共進步!

你的點贊是我持續分享好東西的動力,歡迎點贊!

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索