現代瀏覽器十分複雜,很有運行在操做系統之上的"操做系統"的意思,咱們將盡量用簡單容易理解的例子來簡單歸納它主要的工做邏輯。html
目錄:html5
計算機的核心是 CPU,它承擔了幾乎全部的計算任務。web
你能夠把 CPU 想象成是一個工廠,時刻在運行着。chrome
假設這個工廠的電力有限,同一時刻只能供一個車間使用。這也就意味着,一個車間正在使用,其餘車間都將不會被使用。後端
進程就比如車間,是工廠將要執行的任務。潛臺詞就是說,單個 CPU 任意時刻老是隻能運行一個任務。跨域
一個車間能夠有不少的工人,它們協同完成同一個任務。瀏覽器
線程就是車間裏的工人。安全
假設工人都是很耗電的機器人,靠着分得工廠給的電力進行任務,每一次給的電力恰好夠完成本次的任務,而工廠同一時刻又只能給一個機器人供電。網絡
這幾乎就是單核 CPU 的工做方式了:同一時刻只能作一個工做。架構
但你仍然感受到許多不一樣的任務正在 "同時" 運行着,這是由於當切換任務的速度足夠快時,你將感知不到 CPU 同一時刻只能作一個工做的特性:
咱們的 CPU 就這樣飛速地奔騰着。
每當咱們打開一個應用,就會啓動一個進程。程序也會建立一個或多個線程來幫助它完成工做。
操做系統會爲進程提供一個可以使用的 "一塊" 內存,就像開工廠佔地同樣,全部應用程序的狀態信息都會保存在該私有內存空間中。程序關閉時,相應進程會消失,操做系統也會釋放內存。
進程能夠請求操做系統啓動另外一個進程來執行不一樣的任務。此時內存不一樣區域會分給新進程。
若是兩個進程須要對話,他們能夠經過 進程間通訊(IPC) 來進行。
許多應用程序就是這樣設計的,若是一個工做進程失去響應,該進程就能夠在不中止應用程序的狀況下靠着其餘進程從新啓動。
那麼如何經過進程和線程構建 web 瀏覽器呢?
雖然對於如何構建 web 瀏覽器沒有明確的標準,但如今擁有一個導航欄、輸入框、標籤頁這樣相似的設計倒是不一樣瀏覽器之間默契的共同選擇。
瀏覽器的架構也整體分爲兩類:
如今已經很難看到單進程的架構方式了,由於單進程的瀏覽器須要處理的事情太多(網絡、渲染、管理插件等),極不穩定和安全。所以市面上主流的瀏覽器都已經升級爲多進程的方式。
就拿 Chrome 舉例來講,就採起了下方的架構方式:
多進程的好處顯而易見。好比當你打開了三個標籤頁,其中一個崩潰了,你能夠關掉它而不會影響其餘兩個標籤頁:
而且因爲進程的數據是私有的,因此必定程度上可以保證安全性。
但缺點也顯而易見。咱們上面用車間來類比進程,用工人來類比線程,顯然「建一座車間」比「招聘一個工人」消耗的資源要大得多——哪怕車間只有一個工人——這裏比較明顯的是對內存的消耗。
爲了不過大的內存消耗,Chrome 把一些服務作了聚合:
這樣就能必定程度上減小內存的開銷。
當在瀏覽器中鍵入一個 URL 地址,瀏覽器會作什麼處理呢?
咱們已經習慣了一個連接打開就對應一個外部網站,但它還多是瀏覽器自己的設置頁(如 chrome://settings/
),或是本地硬盤的地址(如 Mac 下的 \
):
因此咱們的第一步就是要判斷這個輸入究竟是個啥:
隨着用戶輸入完畢按下 Enter 鍵,UI 線程知道要啓用網絡去調取網站的信息。網絡線程會負責聯繫目標主機並獲取到信息:
網絡線程獲取信息的過程,發生了不少事,好比 DNS 域名解析、TLS 創建鏈接等,若是不熟悉能夠看看以前的系列文章。
總之網絡線程爲咱們取到了來自網站的響應,大概長這樣:
響應分爲 header 和 payload 兩個部分。header 相似於一本書的版權、做者介紹等相關信息,而 payload 纔是真實的數據內容。
瀏覽器須要根據響應頭裏的 Content-Type
來區分對應內容的類型,例如 text/html
時瀏覽器會對內容進行 HTML 解析,image/png
則調用圖片渲染器。
然而徹底信任網站響應的 Content-Type
是不行的,由於一旦 Content-Type
未指定或者是一個錯誤的值的時候,就會發生未知的錯誤。
因此當收到響應主體(payload)時,網絡線程會在必要時檢查數據的前幾個字節,以確保數據內容與 header 裏標識的數據類型(Content-Type)一致。若是不一致,那麼就須要進行 MIME 類型嗅探來猜想該數據的類型。
當響應是一個 HTML 文件時,此時也會進行安全檢查(SafeBrowsing 檢查)。若是域名和相應數據彷佛匹配到了一個已知的惡意網站,那麼網絡線程會顯示一個警告頁面。
除此以外,還會發生 Cross Origin Read Blocking(CORB)檢查,以確保敏感的跨域數據不被傳給渲染進程。
一旦全部的檢查執行完畢而且網絡線程確信瀏覽器會導航到請求的站點,網絡線程會告訴 UI 線程全部的數據準備完畢。UI 線程會尋找渲染進程去開始渲染 web 頁面。
因爲網絡請求會花費幾百毫秒才獲取迴響應,所以能夠應用一個優化措施。
當第 2 步 UI 線程正發送一個 URL 請求給網絡線程時,它已經知道它們會導航到哪一個站點。在網絡請求的同時,UI 線程並行地嘗試主動尋找或開啓一個渲染進程。
這樣,若是一切按預期進行,渲染進程在網絡線程接受到數據時就已經處於待命狀態。
如今數據和渲染進程已經就緒,瀏覽器進程會發送一個 IPC(進程間通訊)到渲染進程去提交導航。
這時地址欄會更新、標籤頁的歷史記錄也會更新,前進/後退按鈕會走向剛導航過的站點。渲染進程根據 HTML 內容開始解析並渲染頁面。最終您將看到網站設計者設計的網站。
渲染進程涉及 Web 性能的許多方面,流程很是複雜,咱們只作必要的理解。若是您想要深刻了解,能夠在 web.dev
找到相關資源。
渲染進程內部包含主線程、工做線程、合成線程和光柵線程。
在詳細說明以前,請先想象一個這樣的場景:您站在一副簡單繪畫的面前,如何經過打電話來讓您的朋友知道這幅畫究竟長什麼樣子呢?
若是您真打算這麼作,這裏參考 HTML 解析的過程給您提供一些建議。
首先,圖中的元素以及具體元素的屬性分開描述(如:圖裏有一個圓是元素,圓有多大具體在什麼位置等是屬性):
這樣作的好處是可閱讀性變高了,有哪些元素,以及元素哪些屬性一目瞭然,也利於分別維護和修改。(相似於書的目錄和對應內容同樣)
另外是你能夠提煉一些通用的屬性來減小描述:
而後,最好是分層進行描述,由於圖畫是有層次的,光有元素大小、位置等信息是不夠的:
元素實際上就是咱們一般說的 HTML 文件,HTML 文件中包含了描述元素屬性的 CSS 樣式文件。每一個瀏覽器對應常見的樣式都會有默認的樣式。
瀏覽器實際上要知道繪製些什麼元素,每一個元素屬性如何是要分紅三步的:1)經過 HTML 繪製元素樹(俗稱 DOM 樹);2)經過 CSS 文件繪製樣式樹(俗稱 CSSOM 樹);3)綜合兩顆樹繪製渲染樹(俗稱 Render Tree);
如今瀏覽器知道文檔的結構、每一個元素的樣式、頁面的幾何形狀和繪製順序,它是如何繪製頁面的?把這些信息轉換爲屏幕上的像素,咱們稱爲光柵化。
處理這種狀況的一種簡單的方法是,先在光柵化視窗內的畫面,若是用戶滾動頁面,則移動光柵框,並光柵化填充缺乏的部分。這就是 Chrome 首次發佈時處理光柵化的方式。
可是,現代瀏覽器會運行一個更復雜的過程,咱們稱爲合成。
合成是一種將頁面的各個部分分層,分別光柵化,並在稱爲合成線程的單獨線程中合成爲頁面的技術。若是發生滾動,因爲圖層已經光柵化,所以它所要作的只是合成一個新幀。動畫也能夠以相同的方式(移動圖層和合成新幀)實現。
另外須要說明的是如何進行描述是有至關的技巧的。例如「正中心有一個 半徑爲 2 的圓」和「正中心有一個 直徑爲頁面寬度 50% 的圓」是徹底不一樣的:
如何進行組織描述,這須要網站建設者的經驗。
在瀏覽器眼中,用戶的一切行爲都是輸入。不僅僅是滾動鼠標滑輪,或是點擊屏幕、按下按鍵等。
對於瀏覽器進程來講只存在事件和對應座標,只有渲染進程知道頁面究竟長啥樣,以及究竟該如何處理事件。瀏覽器進程只負責把事件和座標發送給渲染進程。
咱們也能夠編寫本身的邏輯文件(js 文件)來監聽某一事件進行對應的處理。而後再統一由渲染進程進行合成。爲了瀏覽流暢,瀏覽器須要保證渲染進程的渲染速度與屏幕刷新率一致(大概每秒 60 幀)。
另外爲了下降主線程中傳遞過量的調用,Chrome 也會把一些連續的事件進行合併。
瀏覽器進程監聽併發送事件給渲染進程進行渲染,這大概就是瀏覽器交互的基本方式。
瀏覽器的複雜遠不是一篇文章能解釋清楚的,本篇文章也只是想讓你們理解瀏覽器的基本過程和原理。儘量使用動圖的形式清晰地表達,但願你們能用餐愉快。
本文大量借鑑了 Chrome 官方 developer 分享的系列文章(下2),若是有想更加深刻了解的小夥伴也能夠閱讀更加硬核的瀏覽器工做原理揭祕文章(下4)
至此,咱們對瀏覽器已經有了至關的瞭解了。後續也會繼續跟你們一塊兒學習計算機網絡的基礎知識,也會嘗試着跟着後端學習路線圖的腳步跟着你們一塊兒學習進階。
這裏是我沒有三顆心臟,歡迎關注公衆號 wmyskxz,2021,與您在 Be Better 的路上共同成長!
(完)