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