摘要:在這些罕見的情況下,解析器必須重新啟動,丟棄之前解碼的內容。標簽包含解析器必須收集的文本,然后發送到腳本引擎進行評估。如果文件內調用了,解析器將重新開始解析過程。事件當解析器完成時,它通過一個名為的事件宣布完成。
瀏覽器基本的工作流程
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!
進入主話題之前,先羅列一下瀏覽器的主要構成:
用戶界面- 包括地址欄、后退/前進按鈕、書簽目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口之外的其他部分
瀏覽器引擎- 用來查詢及操作渲染引擎的接口
渲染引擎- 用來顯示請求的內容,例如,如果請求內容為html,它負責解析html及css,并將解析后的結果顯示出來
網絡- 用來完成網絡調用,例如http請求,它具有平臺無關的接口,可以在不同平臺上工作
UI 后端- 用來繪制類似組合選擇框及對話框等基本組件,具有不特定于某個平臺的通用接口,底層使用操作系統的用戶接口
JS解釋器- 用來解釋執行JS代碼
數據存儲- 屬于持久層,瀏覽器需要在硬盤中保存類似cookie的各種數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術
解析當瀏覽器獲得了資源以后要進行的第一步工作就是 HTML 解析,,它由幾個步驟組成:編碼、預解析、標記和構建樹。
編碼HTTP 響應主體的有效負載可以是從HTML文本到圖像數據的任何內容。解析器的第一項工作是找出如何轉制剛剛從服務器接收到的 bit。
假設我們正在處理一個HTML文檔,解碼器必須弄清楚文本文檔是如何被轉換成比特(bit)的,以便反轉這個過程。
記住,最終即使是文本也會被計算機翻譯成二進制,如上圖所示,在本例中是 ASCII 編碼—定義二進制值,如“01000100”表示字母“D”。
對于文本存在許多可能的編碼—瀏覽器的工作是找出如何正確地解碼文本。服務器應該通過 Content-Type 提供的信息同時在文本文件頭部使用 Byte Order Mark 告知瀏覽器編碼格式。
如果仍然無法確定編碼,瀏覽器還會自行匹配一種解碼格式來處理數據。有時候,解碼格式也會寫在 標簽中。
最壞的情況是,瀏覽器進行了有根據的猜測,然后開始解析之后發現一個矛盾的 標簽。在這些罕見的情況下,解析器必須重新啟動,丟棄之前解碼的內容。瀏覽器有時必須處理舊的 web內容(使用遺留編碼),許多這樣的系統都支持這一點。
我們現在經常在 HTML中使用的文件格式是 UTF-8,那是因為 UTF-8 能較完整的支持Unicode 字符范圍,同時與 CSS、JavaScript 中常見的節字符具有良好的 ASCII 兼容性。一般瀏覽器默認的解碼格式也是 UTF-8。當解碼出錯的時候,我們會看到屏幕上全部都是亂碼字符。
預解析在執行腳本時,其他線程會解析文檔的其余部分,找出并加載需要通過網絡加載的其他資源。通過這種方式,資源可以在并行連接上加載,從而提高總體速度。請注意,預解析器不會修改 DOM 樹,而是將這項工作交由主解析器處理;預解析器只會解析外部資源(例如外部腳本、樣式表和圖片)的引用。
預解析器不是完整的解析器,如,它不理解 HTML 中的嵌套級別或父/子關系。但是,預解析可以識別特定的 HTML 標簽的名稱和屬性,以及 URL。例如,如果你的 HTML 內容中有一個 ,預解析將注意到src屬性,并將獲取這個圖片的請求加到請求隊列中。
請求圖片的速度越快越好,將等待它從網絡到達的時間降到最低。預解析還會注意到 HTML 中的某些顯式請求,比如 preload 和 prefetch 指令,并將它們加入等待隊友中進行處理。
標記化(Tokenization)該算法的輸出結果是 HTML 標記。該算法使用狀態機來表示。每一個狀態接收來自輸入信息流的一個或多個字符,并根據這些字符更新下一個狀態。當前的標記化狀態和樹結構狀態會影響進入下一狀態的決定。這意味著,即使接收的字符相同,對于下一個正確的狀態也會產生不同的結果,具體取決于當前的狀態。該算法相當復雜,無法在此詳述,所以我們通過一個簡單的示例來幫助大家理解其原理。
基本示例 - 將下面的 HTML 代碼標記化:
Hello world
初始狀態是數據狀態。遇到字符 < 時,狀態更改為“標記打開狀態”。接收一個 a-z 字符會創建“起始標記”,狀態更改為“標記名稱狀態”。這個狀態會一直保持到接收 > 字符。在此期間接收的每個字符都會附加到新的標記名稱上。在本例中,我們創建的標記是 html 標記。
遇到 > 標記時,會發送當前的標記,狀態改回“數據狀態”。
標記也會進行同樣的處理。目前 html 和 body 標記均已發出。現在我們回到“數據狀態”。接收到 Hello world 中的 H 字符時,將創建并發送字符標記,直到接收 中的 <。我們將為 Hello world 中的每個字符都發送一個字符標記。現在我們回到“標記打開狀態”。接收下一個輸入字符 / 時,會創建 end tag token 并改為“標記名稱狀態”。我們會再次保持這個狀態,直到接收 >。然后將發送新的標記,并回到“數據狀態”。