前言
現(xiàn)代瀏覽器十分復(fù)雜,頗有運行在操作系統(tǒng)之上的"操作系統(tǒng)"的意思,我們將盡可能用簡單容易理解的例子來簡單概括它主要的工作邏輯。
目錄:
- 進(jìn)程與線程概述;
- 瀏覽器架構(gòu);
- 瀏覽器視角下的輸入;
- 頁面如何渲染;
- 如何進(jìn)行交互;
Part 1. 進(jìn)程與線程概述
計算機(jī)的核心是 CPU,它承擔(dān)了幾乎所有的計算任務(wù)。
你可以把 CPU 想象成是一個工廠,時刻在運行著。
假設(shè)這個工廠的電力有限,同一時刻只能供一個車間使用。這也就意味著,一個車間正在使用,其他車間都將不會被使用。
進(jìn)程就好比車間,是工廠將要執(zhí)行的任務(wù)。潛臺詞就是說,單個 CPU 任意時刻總是只能運行一個任務(wù)。
一個車間可以有很多的工人,它們協(xié)同完成同一個任務(wù)。
線程就是車間里的工人。
假設(shè)工人都是很耗電的機(jī)器人,靠著分得工廠給的電力進(jìn)行任務(wù),每一次給的電力剛好夠完成本次的任務(wù),而工廠同一時刻又只能給一個機(jī)器人供電。
這幾乎就是單核 CPU 的工作方式了:同一時刻只能做一個工作。
但你仍然感覺到許多不同的任務(wù)正在 "同時" 運行著,這是因為當(dāng)切換任務(wù)的速度足夠快時,你將感知不到 CPU 同一時刻只能做一個工作的特性:
我們的 CPU 就這樣飛速地奔騰著。
每當(dāng)我們打開一個應(yīng)用,就會啟動一個進(jìn)程。程序也會創(chuàng)建一個或多個線程來幫助它完成工作。
操作系統(tǒng)會為進(jìn)程提供一個可使用的 "一塊" 內(nèi)存,就像開工廠占地一樣,所有應(yīng)用程序的狀態(tài)信息都會保存在該私有內(nèi)存空間中。程序關(guān)閉時,相應(yīng)進(jìn)程會消失,操作系統(tǒng)也會釋放內(nèi)存。
進(jìn)程可以請求操作系統(tǒng)啟動另一個進(jìn)程來執(zhí)行不同的任務(wù)。此時內(nèi)存不同區(qū)域會分給新進(jìn)程。
如果兩個進(jìn)程需要對話,他們可以通過 進(jìn)程間通信(IPC) 來進(jìn)行。
許多應(yīng)用程序就是這樣設(shè)計的,如果一個工作進(jìn)程失去響應(yīng),該進(jìn)程就可以在不停止應(yīng)用程序的情況下靠著其他進(jìn)程重新啟動。
Part 2. 瀏覽器架構(gòu)
那么如何通過進(jìn)程和線程構(gòu)建 web 瀏覽器呢?
雖然對于如何構(gòu)建 web 瀏覽器沒有明確的標(biāo)準(zhǔn),但現(xiàn)在擁有一個導(dǎo)航欄、輸入框、標(biāo)簽頁這樣類似的設(shè)計卻是不同瀏覽器之間默契的共同選擇。
瀏覽器的架構(gòu)也總體分為兩類:
現(xiàn)在已經(jīng)很難看到單進(jìn)程的架構(gòu)方式了,因為單進(jìn)程的瀏覽器需要處理的事情太多(網(wǎng)絡(luò)、渲染、管理插件等),極不穩(wěn)定和安全。因此市面上主流的瀏覽器都已經(jīng)升級為多進(jìn)程的方式。
就拿 Chrome 舉例來說,就采取了下方的架構(gòu)方式:
- 最頂層是瀏覽器進(jìn)程,負(fù)責(zé)協(xié)調(diào)處理其他進(jìn)程模塊的任務(wù)。
- UI 進(jìn)程負(fù)責(zé)控制地址欄、標(biāo)簽頁等;
- 渲染進(jìn)程控制標(biāo)簽頁內(nèi)網(wǎng)站的展示。
- 插件進(jìn)程控制站點使用的任意插件,比如:Flash。
- GPU 進(jìn)程單獨處理來自不同應(yīng)用發(fā)送的繪制請求。
- ....
多進(jìn)程的好處顯而易見。比如當(dāng)你打開了三個標(biāo)簽頁,其中一個崩潰了,你可以關(guān)掉它而不會影響其他兩個標(biāo)簽頁:
并且由于進(jìn)程的數(shù)據(jù)是私有的,所以一定程度上能夠保證安全性。
但缺點也顯而易見。我們上面用車間來類比進(jìn)程,用工人來類比線程,顯然「建一座車間」比「招聘一個工人」消耗的資源要大得多——哪怕車間只有一個工人——這里比較明顯的是對內(nèi)存的消耗。
為了避免過大的內(nèi)存消耗,Chrome 把一些服務(wù)做了聚合:
這樣就能一定程度上減少內(nèi)存的開銷。
Part 3. 瀏覽器視角下的輸入
當(dāng)在瀏覽器中鍵入一個 URL 地址,瀏覽器會做什么處理呢?
第一步:處理輸入
我們已經(jīng)習(xí)慣了一個鏈接打開就對應(yīng)一個外部網(wǎng)站,但它還可能是瀏覽器本身的設(shè)置頁(如 chrome://settings/
),或是本地硬盤的地址(如 Mac 下的 \
):
所以我們的第一步就是要判斷這個輸入到底是個啥:
第二步:開始導(dǎo)航
隨著用戶輸入完畢按下 Enter 鍵,UI 線程知道要啟用網(wǎng)絡(luò)去調(diào)取網(wǎng)站的信息。網(wǎng)絡(luò)線程會負(fù)責(zé)聯(lián)系目標(biāo)主機(jī)并獲取到信息:
網(wǎng)絡(luò)線程獲取信息的過程,發(fā)生了很多事,比如 DNS 域名解析、TLS 建立連接等,如果不熟悉可以看看之前的系列文章。
第三步:讀取響應(yīng)
總之網(wǎng)絡(luò)線程為我們?nèi)〉搅藖碜跃W(wǎng)站的響應(yīng),大概長這樣:
響應(yīng)分為 header 和 payload 兩個部分。header 類似于一本書的版權(quán)、作者介紹等相關(guān)信息,而 payload 才是真實的數(shù)據(jù)內(nèi)容。
瀏覽器需要根據(jù)響應(yīng)頭里的 Content-Type
來區(qū)分對應(yīng)內(nèi)容的類型,例如 text/html
時瀏覽器會對內(nèi)容進(jìn)行 HTML 解析,image/png
則調(diào)用圖片渲染器。
然而完全信任網(wǎng)站響應(yīng)的 Content-Type
是不行的,因為一旦 Content-Type
未指定或者是一個錯誤的值的時候,就會發(fā)生未知的錯誤。
所以當(dāng)收到響應(yīng)主體(payload)時,網(wǎng)絡(luò)線程會在必要時檢查數(shù)據(jù)的前幾個字節(jié),以確保數(shù)據(jù)內(nèi)容與 header 里標(biāo)識的數(shù)據(jù)類型(Content-Type)一致。如果不一致,那么就需要進(jìn)行 MIME 類型嗅探來猜測該數(shù)據(jù)的類型。
當(dāng)響應(yīng)是一個 HTML 文件時,此時也會進(jìn)行安全檢查(SafeBrowsing 檢查)。如果域名和相應(yīng)數(shù)據(jù)似乎匹配到了一個已知的惡意網(wǎng)站,那么網(wǎng)絡(luò)線程會顯示一個警告頁面。
除此之外,還會發(fā)生 Cross Origin Read Blocking(CORB)檢查,以確保敏感的跨域數(shù)據(jù)不被傳給渲染進(jìn)程。
第四步:查找渲染進(jìn)程
一旦所有的檢查執(zhí)行完畢并且網(wǎng)絡(luò)線程確信瀏覽器會導(dǎo)航到請求的站點,網(wǎng)絡(luò)線程會告訴 UI 線程所有的數(shù)據(jù)準(zhǔn)備完畢。UI 線程會尋找渲染進(jìn)程去開始渲染 web 頁面。
由于網(wǎng)絡(luò)請求會花費幾百毫秒才獲取回響應(yīng),因此可以應(yīng)用一個優(yōu)化措施。
當(dāng)?shù)?2 步 UI 線程正發(fā)送一個 URL 請求給網(wǎng)絡(luò)線程時,它已經(jīng)知道它們會導(dǎo)航到哪個站點。在網(wǎng)絡(luò)請求的同時,UI 線程并行地嘗試主動尋找或開啟一個渲染進(jìn)程。
這樣,如果一切按預(yù)期進(jìn)行,渲染進(jìn)程在網(wǎng)絡(luò)線程接受到數(shù)據(jù)時就已經(jīng)處于待命狀態(tài)。
第五步:提交導(dǎo)航
現(xiàn)在數(shù)據(jù)和渲染進(jìn)程已經(jīng)就緒,瀏覽器進(jìn)程會發(fā)送一個 IPC(進(jìn)程間通信)到渲染進(jìn)程去提交導(dǎo)航。
這時地址欄會更新、標(biāo)簽頁的歷史記錄也會更新,前進(jìn)/后退按鈕會走向剛導(dǎo)航過的站點。渲染進(jìn)程根據(jù) HTML 內(nèi)容開始解析并渲染頁面。最終您將看到網(wǎng)站設(shè)計者設(shè)計的網(wǎng)站。
Part 4. 頁面如何渲染
渲染進(jìn)程涉及 Web 性能的許多方面,流程非常復(fù)雜,我們只做必要的理解。如果您想要深入了解,可以在 web.dev
找到相關(guān)資源。
渲染進(jìn)程內(nèi)部包含主線程、工作線程、合成線程和光柵線程。
在詳細(xì)說明之前,請先想象一個這樣的場景:您站在一副簡單繪畫的面前,如何通過打電話來讓您的朋友知道這幅畫究竟長什么樣子呢?
如果您真打算這么做,這里參考 HTML 解析的過程給您提供一些建議。
首先,圖中的元素以及具體元素的屬性分開描述(如:圖里有一個圓是元素,圓有多大具體在什么位置等是屬性):
這樣做的好處是可閱讀性變高了,有哪些元素,以及元素哪些屬性一目了然,也利于分別維護(hù)和修改。(類似于書的目錄和對應(yīng)內(nèi)容一樣)
另外是你可以提煉一些通用的屬性來減少描述:
然后,最好是分層進(jìn)行描述,因為圖畫是有層次的,光有元素大小、位置等信息是不夠的:
元素實際上就是我們通常說的 HTML 文件,HTML 文件中包含了描述元素屬性的 CSS 樣式文件。每個瀏覽器對應(yīng)常見的樣式都會有默認(rèn)的樣式。
瀏覽器實際上要知道繪制些什么元素,每個元素屬性如何是要分成三步的:1)通過 HTML 繪制元素樹(俗稱 DOM 樹);2)通過 CSS 文件繪制樣式樹(俗稱 CSSOM 樹);3)綜合兩顆樹繪制渲染樹(俗稱 Render Tree);
現(xiàn)在瀏覽器知道文檔的結(jié)構(gòu)、每個元素的樣式、頁面的幾何形狀和繪制順序,它是如何繪制頁面的?把這些信息轉(zhuǎn)換為屏幕上的像素,我們稱為光柵化。
處理這種情況的一種簡單的方法是,先在光柵化視窗內(nèi)的畫面,如果用戶滾動頁面,則移動光柵框,并光柵化填充缺少的部分。這就是 Chrome 首次發(fā)布時處理光柵化的方式。
但是,現(xiàn)代瀏覽器會運行一個更復(fù)雜的過程,我們稱為合成。
合成是一種將頁面的各個部分分層,分別光柵化,并在稱為合成線程的單獨線程中合成為頁面的技術(shù)。如果發(fā)生滾動,由于圖層已經(jīng)光柵化,因此它所要做的只是合成一個新幀。動畫也可以以相同的方式(移動圖層和合成新幀)實現(xiàn)。
另外需要說明的是如何進(jìn)行描述是有相當(dāng)?shù)募记傻?/strong>。例如「正中心有一個 半徑為 2 的圓」和「正中心有一個 直徑為頁面寬度 50% 的圓」是完全不同的:
如何進(jìn)行組織描述,這需要網(wǎng)站建設(shè)者的經(jīng)驗。
Part 5. 如何進(jìn)行交互
在瀏覽器眼中,用戶的一切行為都是輸入。不單單是滾動鼠標(biāo)滑輪,或是點擊屏幕、按下按鍵等。
對于瀏覽器進(jìn)程來說只存在事件和對應(yīng)坐標(biāo),只有渲染進(jìn)程知道頁面究竟長啥樣,以及究竟該如何處理事件。瀏覽器進(jìn)程只負(fù)責(zé)把事件和坐標(biāo)發(fā)送給渲染進(jìn)程。
我們也可以編寫自己的邏輯文件(js 文件)來監(jiān)聽某一事件進(jìn)行對應(yīng)的處理。然后再統(tǒng)一由渲染進(jìn)程進(jìn)行合成。為了瀏覽流暢,瀏覽器需要保證渲染進(jìn)程的渲染速度與屏幕刷新率一致(大概每秒 60 幀)。
[站外圖片上傳中...(image-cac9e8-1613649340231)]
另外為了降低主線程中傳遞過量的調(diào)用,Chrome 也會把一些連續(xù)的事件進(jìn)行合并。
瀏覽器進(jìn)程監(jiān)聽并發(fā)送事件給渲染進(jìn)程進(jìn)行渲染,這大概就是瀏覽器交互的基本方式。
后記
瀏覽器的復(fù)雜遠(yuǎn)不是一篇文章能解釋清楚的,本篇文章也只是想讓大家理解瀏覽器的基本過程和原理。盡可能使用動圖的形式清晰地表達(dá),希望大家能用餐愉快。
本文大量借鑒了 Chrome 官方 developer 分享的系列文章(下2),如果有想更加深入了解的小伙伴也可以閱讀更加硬核的瀏覽器工作原理揭秘文章(下4)
至此,我們對瀏覽器已經(jīng)有了相當(dāng)?shù)牧私饬恕:罄m(xù)也會繼續(xù)跟大家一起學(xué)習(xí)計算機(jī)網(wǎng)絡(luò)的基礎(chǔ)知識,也會嘗試著跟著后端學(xué)習(xí)路線圖的腳步跟著大家一起學(xué)習(xí)進(jìn)階。
[站外圖片上傳中...(image-9c5f9d-1613649340231)]
這里是我沒有三顆心臟,歡迎關(guān)注公眾號 wmyskxz,2021,與您在 Be Better 的路上共同成長!
參考資料
- 進(jìn)程與線程的簡單解釋 - http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
- 轉(zhuǎn)載:現(xiàn)代瀏覽器內(nèi)部揭秘 - https://hasaki.xyz/blog/2020-01-20-%E8%BD%AC%E8%BD%BD%E7%8E%B0%E4%BB%A3%E6%B5%8F%E8%A7%88%E5%99%A8%E5%86%85%E9%83%A8%E6%8F%AD%E7%A7%98/
- 深入淺出瀏覽器渲染原理 - https://blog.fundebug.com/2019/01/03/understand-browser-rendering/
- 瀏覽器工作原理幕后揭秘 - https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
(完)