From URL to Interactive(二)---從標籤到DOM(Tags to DOM)

這是《From URL to Interactive》系列文章的第一篇《Server to Client》。《From URL to Interactive》是個引子就不譯了,文章主要基於windows自帶的瀏覽器Eage爲基礎介紹現代瀏覽器對HTML從請求、鏈接、加載、解析、渲染、交互的過程。分階段介紹:

From URL to Interactive(一)---從服務器到客戶端(Server to Client)

From URL to Interactive(二)---從標籤到DOM(Tags to DOM

From URL to Interactive(三)---從大括號到像素(Braces to Pixels)

From URL to Interactive(四)---從var到及時編譯(Var to JIT)

 

篇原文地址:https://alistapart.com/article/tags-to-dom

-----------------------------------------------------------------------------------------------------------------------------

在我們之前的“ 服務器到客戶端 ” 部分中,我們瞭解瞭如何向服務器請求URL並瞭解有助於優化相關資源交付的許多條件和緩存。一旦瀏覽器引擎最終獲得資源,它就需要開始將其轉換爲呈現的網頁。在這一部分中,我們主要關注HTML資源,以及如何將HTML標籤(tags)轉換爲最終將在屏幕上呈現的構建塊。

解析

一旦內容通過網絡系統從服務器傳送到客戶端,其第一站就是HTML解析器,它由一些協同工作的系統組成:編碼,預解析,標記化和樹構造。解析器是建築項目隱喻的一部分,我們在其中瀏覽所有原材料:拆包; 卸載托盤、管道、電線等; 在把所有東西都交給那些從事框架,管道,電氣等工作的專家之前,先澆一下粉底

編碼

HTTP響應主體(response body)的有效負載可以是從HTML文本到圖像數據的任何內容。解析器的第一項工作是弄清楚如何解釋剛從服務器接收的bits(數據流)。假設我們正在處理HTML文檔,解碼器必須知道如何將文本文檔轉換爲bits,從而能反轉該過程(即由bits還原文本)。

(請記住,最終連文本都必須在計算機中翻譯成二進制。編碼(在裏指ASCII編碼)定義了二進制值,如“01000100”表示字母“D”,如上圖所示。)對於文本有很多可選的編碼 - 瀏覽器需要弄清楚如何正確解碼文本。服務器應通過Content-Type 頭信息提供提示,並且可以分析前導位(對於byte order mark或BOM)。如果仍然無法確定編碼,則瀏覽器可以使用啓發式做最可能的猜測。有時唯一明確的答案來自(編碼的)內容本身的<meta>標籤中。最糟糕的情況是,瀏覽器進行了有根據的猜測,然後發現了與解析的<meta>標籤矛盾。在這種極少發生的情況下,解析器會丟棄以前解碼的內容,重新啓動。瀏覽器有時必須處理舊的Web內容(使用遺留編碼),並且很多系統都適當的支持該能力(譯者:指支持對舊編碼解析)。

現在爲Web保存HTML文檔時,編碼的選擇已經很明確了:使用UTF-8編碼。爲什麼?它很好地支持完整的Unicode字符範圍,與CSS,HTML和JavaScript等語言常使用的單字節字符的ASCII編碼具有良好的兼容性,UTF-8很可能是瀏覽器的反饋的默認值。您可以判斷編碼何時出錯,因爲錯誤的編碼會讓文本無法正確呈現(通常在清晰可見的文本中會看到垃圾字符或框)。

預解析/掃描

一旦編碼已知,解析器就開始初始預解析步驟來掃描內容,預解析的目的是最小化其他資源的往返延遲。預解析器不是完整的解析器; 例如,它不瞭解HTML中的嵌套級別或父/子關係。但是,預解析器卻可以識別特定的HTML標記名稱和屬性以及URL。例如,如果您<img src="https://somewhere.example.com/​images/​dog.png" alt="">的HTML內容中有某個位置,則預解析器會注意到該src屬性,並通過網絡系統將dog.png資源請求隊列。這樣可以儘快地請求dog.png,最大限度地減少等待它從網絡到達所需的時間。預解析器還可以注意到HTML中的某些顯式請求,例如preloadprefetch指令,並將它們放入處理隊列。

TOKENIZATION

Tokenization是解析HTML的前半部分。它涉及將markup轉換爲單獨的token,例如“開始標記”(begin tag),“結束標記”(end tag),“文本運行”(text run),“註釋”(comment)等,這些token被送入解析器的下一個狀態。tokenizer是一種狀態機,它在HTML語言的不同狀態之間轉換,例如“在標記打開狀態”(<|video controls>),“在屬性名稱狀態”(<video con | trols>)和“在屬性名稱狀態之後”(<video controls | >),迭代地這樣做因爲HTML標記文本文檔中的每個字符都會被讀取。

(在每個示例標記中,豎線“|”標明瞭tokenizer的位置。)

 

在HTML規範(見“12.2.5 Tokenization”)目前爲tokenizer定義了80個獨立的狀態。tokenizer和parser非常適應:兩者都可以處理任何文本內容並將其轉換爲HTML文檔 - 即使文本中的代碼不是有效的HTML。像這樣的彈性的能力是使網絡對於所有技能水平的開發人員都友好的功能之一。但是,tokenizer和parser的彈性的缺點是您可能無法始終獲得預期的結果,這可能會導致一些細微的編程錯誤。(檢查HTML驗證器中的代碼可以幫助您避免這樣的錯誤。)

對於那些喜歡採用更加黑白的方法來標記語言正確性的人來說,瀏覽器內置了一種替代解析機制,可將任何故障視爲災難性故障(意味着任何故障都會導致內容無法呈現)。此語法分析模式使用XML規則來處理HTML,並且可以通過在發送給瀏覽器的文檔中添加“application/xhtml+xml”的MIME type來實現(或任何在HTML namespace中使用的基於XML MIME類型的elements)。

瀏覽器可以將pre-parser和tokenization兩步組合在一起作爲優化。

解析/構建樹

瀏覽器需要一個網頁的內部(in-memory)表示,並且在DOM標準中,Web標準確切地定義了表示應該是什麼形狀。parser的職責是在前一步驟中獲取由tokenizer創建的tokens,並以適當的方式創建對象並將其插入到文檔對象模型(DOM)中(具體使用其狀態機的23個獨立狀態 ;請參閱“12.2.6.4在HTML內容中解析標記的規則”)。DOM被組織成樹型結構,因此該過程有時被稱爲構造樹。(另外,Internet Explorer 在其歷史的大部分時間裏都沒有使用樹結構。)

HTML解析由於要進行各種錯誤處理而變得複雜,這些錯誤處理確保舊HTML內容在現代瀏覽器中具有兼容的結構。例如,許多HTML標記都隱含了結束標記,這意味着如果您不提供結束標記,瀏覽器會自動爲您關閉匹配標記。例如,考慮這個HTML:

這樣可以確保結果樹中的兩個段落對象是兄弟節點,而忽略第二個打開的標籤。HTML表可能是解析器規則試圖確保其具有合理結構的最複雜的對象。

儘管有複雜的解析規則,單一旦DOM樹創建好,就不再強制執行創建“正確”HTML結構的規則。使用JavaScript,網頁幾乎可以以任何方式重新排列DOM樹,即使它沒有意義!(例如,添加表格單元格作爲<video>標記的子級)。渲染系統負責弄清楚如何處理那些奇怪的不一致的地方。

HTML解析中的另一個複雜因素是JavaScript可以在解析器處於工作時添加更多要解析的內容。<script>標記包含解析器必須收集的文本,然後發送到腳本引擎進行評估。當腳本引擎解析並評估腳本文本時,解析器會等待。如果腳本評估包括調用document.writeAPI,第二個HTML解析器必須開始運行(reentrantly)。要快速重新審視我們的建築比喻,<script>和document.write要求停止所有正在進行的工作,回到商店,以獲得一些我們沒有意識到所需要的其他材料。當我們離開商店後,施工的所有進展都將停滯不前。

所有這些複雜性使得編寫兼容的HTML解析器成爲一件非常重要的事情。

事件(Events)

當解析器完成時,它通過一個名爲DOMContentLoaded的事件宣告它的完成。Events是瀏覽器內置的廣播系統通過JavaScript能監聽和響應的。在我們的構造比喻中,事件是各種工人在遇到問題或完成任務時帶給工頭的報告。比如DOMContentLoaded,有各種各樣的event表明網頁中的重大狀態變化,例如load(意味着解析完成,解析器請求的所有資源,如圖像,CSS,視頻等,已經下載)和unload(意味着網頁即將關閉)。許多事件是特定於用戶輸入,例如用戶觸摸屏幕(pointerdown,pointerup,及其他),使用鼠標(mouseover,mousemove和其他),或在鍵盤上輸入(keydown,keyup,和keypress)。

瀏覽器在DOM中創建一個事件對象,其中包含大量有用的狀態信息(例如屏幕上觸摸的位置,按下的鍵盤上的鍵等等),並“觸發”該事件。然後運行恰好正在偵聽該事件的JavaScript代碼並將事件對象一起提供給該JavaScript。

DOM的樹型結構方便的讓樹中的任何級別(如,樹的根,樹的葉子,以及他們之間的任何地方)可以通過監聽event來過濾(“filter”)頻繁的event響應(譯者:即對event的選擇性處理,因爲root下的子節點event都會反映到根部,所以這裏講頻繁的event的響應)。瀏覽器首先確定樹中觸發事件的位置(意味着哪個DOM對象,例如特定<input>控件),然後計算從樹的根開始的事件的路由,然後向下遍歷每個分支,直到它到達目標(例如<input>),然後沿着相同的路徑返回到根。然後沿着路徑的每個對象觸發其事件偵聽器,這使得樹的根處的偵聽器將“看到”比葉子處的特定偵聽器更多的事件。

 

某些事件也可以取消,例如,如果表單未正確填寫,則可以提供停止表單提交的功能。(submit從<form>元素觸發事件, JavaScript偵聽器可以檢查表單,並且如果字段爲空或無效,則可以選擇取消事件。)

DOM

HTML語言提供了豐富的功能集,這些擴展遠遠超出瞭解析器處理的範圍。解析器構建了包含元素之間的包含關係,以及元素的初始狀態(屬性)的結構。這些結構和狀態的組合,就足以提供基本的渲染和一些交互能力(如通過像<textarea>,<video>,<button>等等這樣的內置控件)。但是如果沒有添加CSS和JavaScript,網絡將非常枯燥(和靜態)。DOM爲HTML元素和與HTML無關的其他對象提供了額外的功能層。

在建築比喻中,解析器組裝了最終的建築物 - 所有的牆壁,門,地板和天花板都已安裝完畢,管道,電氣,天然氣等都已準備就緒。你可以打開門窗,打開和關閉燈,但結構非常簡單。例如,CSS在牆壁和踢腳線上提供內部細節顏色。(我們將在下一部分中介紹CSS。)JavaScript允許訪問DOM - 內部的所有傢俱和設備,以及建築物外的服務,例如郵箱,存儲棚和工具,太陽能電池板,水好吧等我們接下來描述“傢俱”和外面的“服務”。

元素接口

當解析器構造要放入樹中的對象時,它會查找元素的名稱(和命名空間)並找到匹配的HTML接口來圍繞對象。

接口爲基本的HTML元素添加特定於其類型或元素類型的特性。一些通用特性包括:

  1. 訪問代表元素子元素的全部或子集的HTML集合;
  2. 搜索元素的屬性,子元素和父元素的能力;
  3. 而且重要的是,創建新元素的方法(不使用解析器),並將它們附加到樹上(或從中移除)。

對於特定元素,比如<table>,該接口包含用於查找表中所有行,列和單元格的其他特定於表的功能,以及用於從表中刪除和添加行和單元格的快捷方式。同樣,<canvas>界面具有繪製線條,形狀,文本和圖像的功能。使用這些API僅靠HTML標記是不行的,需要用到JavaScript。

在解析結束後,通過上述API對樹進行的任何DOM更改(例如樹中元素的層次結構位置,通過切換屬性名稱或值的元素狀態,或來自元素接口的任何API操作)將觸發瀏覽器系統的連鎖反應,其工作是分析更改並儘快更新你在屏幕上看到的內容。爲了使這些重複更新快速有效,該DOM樹保持了許多優化,例如:

  1. 通過數字表示公共元素名稱和屬性(使用哈希表進行快速識別);
  2. 記錄元素經常訪問的子節點集合的緩存(用於快速子元素迭代);
  3. 子樹更改跟蹤,以最小化整個樹的哪些部分變得“髒”(並且需要重新驗證)

其他API

DOM中的HTML元素及其接口是瀏覽器在屏幕上顯示內容的唯一機制。CSS可以影響佈局,但僅限於HTML元素中存在的內容。最終,如果你想在屏幕上看到內容,它必須通過作爲樹的一部分的HTML接口來完成。“(對於那些想知道可縮放矢量圖形(SVG)和MathML語言的人 - 這些元素也必須添加到樹中-爲了簡潔我這裏將跳過它們。)

我們學習瞭解析器是如何將HTML從服務器獲取到DOM樹中的一種方式,以及DOM中的元素接口如何用於在事後添加,刪除和修改該樹。然而,瀏覽器的可編程DOM非常龐大,並不僅限於HTML元素接口。

瀏覽器的DOM範圍可與應用程序在任何操作系統中使用的功能集相媲美。比如下面這些(但不限於)事情:

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

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

繼續看標記(Moving on From markup)

在這一部分中,您已經瞭解瞭解析和樹構造如何爲DOM創建基礎:從網絡接收的HTML標記的有狀態內存表示。

有了DOM模型,諸如事件模型和元素API之類的服務使Web開發人員可以隨時更改DOM結構。每次更改都會開始一系列“重建”工作,更新DOM只是第一步。

回到建築類比,現場原材料已形成建築物的結構框架,並按照正確的尺寸建造,安裝了內部管道,電氣和其他服務,但對建築物的最終結構還沒有真正的意義外觀 - 外觀和室內設計。

在下一部分中,我們將介紹瀏覽器如何將包含CSS的DOM樹作爲佈局引擎(layout engine)的輸入,並將樹轉換爲最終可以在屏幕上看到的內容。

 

關於作者

Travis Leithead

Travis在Microsoft Edge Web平臺上工作,專注於DOM API及其相關標準。他幫助協調其他人蔘與標準的指定,並擔任W3C技術架構小組的成員,主要負責審查新Web標準的提案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章