天天看點

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

大家好,我是​

​零一​

​,每個開發者(尤其是前端工程師)或多或少會跟浏覽器打交道,那麼你們有沒有想過去深入了解浏覽器呢?無論是因為好奇還是為了面試,相信你們一定在網上搜過不少關于浏覽器相關的知識和文章,或者也買過不少的課程。但很少有文章能生動形象地帶你去了解浏覽器的點點滴滴。

作者:獨釣寒江雪

本文用​

​47​

​張圖帶你了解「浏覽器的發展史」、「浏覽器的架構」、「浏覽器的基本原理」以及 「浏覽器的其它小知識」

🚩 正文開始

浏覽器的主要功能就是向伺服器送出請求,在浏覽器視窗中展示HTML文檔、PDF、圖檔、視訊等網絡内容。這些網絡資源的位置由使用者使用 URI(統一資源标示符)來指定指定。

或許在大多數人眼中,浏覽器是這樣的:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

大多數人眼中的浏覽器

「一個展示前端,一個未知的中間層連接配接着網絡世界」;甚至,網絡世界也可以省略:一台顯示器,一個神秘的幕後黑盒。

如果你是一個前端開發者,甚至每天浏覽器陪伴你度過的時光比女朋友陪伴你的都要久,想想那每一個令人“不是那麼期待”的早晨,每一個争分奪秒完成任務的黃昏,隻有浏覽器和編輯器一直是你忠實的夥伴。而「就連你一直離不開的VS Code編輯器,甚至也與浏覽器有着莫大的淵源」。

螢幕前的朋友,你熟悉自己身邊的那些人嗎,熟悉那些與你朝夕相伴的朋友嗎?也許熟悉,也許不,那麼,你是否願意花些時間來熟悉一下這個在大量時間裡與你有着莫大交集的浏覽器的内心世界呢?

今天,我們就來一探究竟,走進這個我們與網絡連接配接最緊密的中間地帶。全文行文結構大概如下:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

目錄結構

浏覽器發展簡史

浏覽器的誕生與發展

也許你知道,第一款浏覽器 —— WorldWideWeb,誕生于1990年。但是現代浏覽器的雛形卻孕育于 1980s年代。

一位名叫蒂姆·伯納斯-李的英國科學家在 1980 年代初期建立了一個名為 Inquire 的計算機程式,當時他在總部位于瑞士的歐洲核研究組織(CERN,以其法文字母表示)工作。該計劃旨在「使在 CERN 工作的許多不同個人更容易共享資訊」。

1990年,第一款浏覽器問世于Tim Berners-Lee 在 CERN 工作期間。您可能想知道 Web 浏覽器到底是什麼,簡而言之,它是一個計算機程式,其目的是顯示和檢索資料。使用配置設定給存儲在網絡伺服器上的每個資料集(網頁)的 URL,它可以做到這一點。是以這意味着「當您在浏覽器中輸入内容時,您實際上是在輸入位址」,浏覽器将使用該位址來擷取您想要檢視的資訊。「浏覽器的另一個關鍵功能是以易于了解的方式向您解釋和呈現計算機代碼」。

下圖簡單羅列了截止2020年浏覽器的發展簡史:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Timeline_of_the_Web_Browsers

早期比較有名、有意義的浏覽器主要包括Erwise、ViolaWWW、Mosaic、Netscape Navigator:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

The-Early-Browsers

1990年浏覽器誕生之後的故事,想必您已經早有耳聞:

  • 「NCSA Mosaic」,或簡稱 Mosaic,是網際網路曆史上「第一個獲普遍使用和能夠顯示圖檔的網頁浏覽器」。它由伊利諾伊大學厄巴納-香槟分校的NCSA組織在1993年發表,并于1997年1月7日正式終止開發和支援,這款浏覽器在當時大受歡迎。Mosaic的出現,算是點燃了後期網際網路熱潮的火種之一。後來 Netscape Navigator 浏覽器的開發,聘用了許多原有的 Mosaic 浏覽器工程師,但是沒有采用 Mosaic 網頁浏覽器的任何代碼。而傳承網景浏覽器代碼的後裔為Firefox浏覽器。
  • Marc Andreesen 與同僚 Jim Clark 于 1994 年成立了一家公司,當時 Mosaic 還是最流行的浏覽器,它們計劃打造出一個比 Mosaic 更好的浏覽器,占領市場,讓他們變得富有,并改變曆史。他們的第一個浏覽器被稱為 Mosaic Netscape 0.9,不久更名 Netscape。得益于 JavaScript(JavaScript誕生于1995年,它是Netscape的Brendan Eich 僅花費十天設計實作的。) 和“partial-screen loading”(即使頁面未完全加載,使用者也可以開始閱讀頁面上的詳細資訊,這一個新概念極大地豐富了線上體驗)等功能,它很快成為市場上司者,占據了浏覽器市場上一半的份額,最瘋狂的時候,網景浏覽器的市場佔有率接近百分之九十。
1995年8月9日,網景公開募股,最初的價格是14美元一股,但後來陰差陽錯,改為28美元一股發行,當天收盤時,網景的股票成了75美元一股,網景成為了當時世界上市值最高的網際網路公司,Netscape 的 IPO 也助長了日益增長的網絡泡沫。
  • Netscape 最初的成功向那些在計算機和網際網路領域工作的人證明時代已經永遠改變了,這讓當時業内最強大的參與者感到震驚,一家名為 Microsoft 的西雅圖公司就是其中之一。計算機将通過浏覽器運作,浏覽器可以在任何機器上運作,進而使軟體行業民主化并降低其相當大的進入壁壘,這導緻許多人猜測「作業系統的時代已經結束」。Netscape 對微軟來說是一個挑戰,微軟在 1990 年代後期建立了自己的浏覽器 Internet Explorer,當時的IE和現在一樣,通常被視為劣質産品。由于「微軟已經建立了銷售其專有作業系統 Windows 的帝國」,是以将這種由 Netscape 等公司帶頭的發展視為一種威脅。微軟通過對其産品的大量投資,使其與 Netscape 一樣好,成功地迅速扭轉了浏覽器行業的局面。Windows 計算機在釋出時已經安裝了 Internet Explorer(Microsoft 的浏覽器),這使其能夠在市場上占據一席之地并不斷發展壯大,最終在浏覽器領域取得了勝利,這便是著名的「第一次浏覽器大戰」。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Market_Share_During_the_Browser_Wars

市場佔有率的快速下滑導緻 Netscape 被出售給了 AOL,2003年7月,網景解散,就在解散的當天,Mozilla基金會成立,2004年基于Mozilla源碼的Firefox首次登台,拉開了第二次浏覽器大戰的序幕。2008 年Netscape最終滅絕,「當年的浏覽器帝國正式退出了曆史的舞台」。

到 2003 年,微軟的 Internet Explorer 控制了 92% 以上的市場,完全扭轉了 1995 年的局面。然而,雖然微軟在不到十年的時間裡成功地完全接管了浏覽器市場,但很快就會出現其他競争,再次重塑網絡浏覽器的曆史。

  • 微軟在 1990 年代後期崛起并讓 Netscape 等公司屈服之後,浏覽器的曆史似乎已經走到了盡頭。然而,正如最初釋出後的情況一樣,Internet Explorer 正在成為劣質産品。谷歌于 2008 年推出了其專有浏覽器——Chrome。到 2012 年底,即推出僅四年後,谷歌 Chrome 浏覽器憑借其易用性、跨平台功能、速度以及與标簽和書簽相關的特殊功能,取代 Internet Explorer 成為最受歡迎的浏覽器。
  • 在 2000 年代初期,可能是在微軟将浏覽器附加到其作業系統之後,Apple 釋出了 Safari,一種專為 Mac 設計的浏覽器,并成為目前市場上第二大浏覽器。
  • Internet Explorer 的流行度在 2000 年代後期逐漸減少,主要是因為它變得緩慢和過時,而 Microsoft 發現自己現在似乎已經是在外面觀察浏覽器世界。該公司不想繼續錯過,于是着手解決這個問題,但發現一個關鍵問題是“Internet Explorer”這個名字已經成為劣質浏覽器的同義詞。是以,為了嘗試重新進入遊戲,微軟不得不重新命名,是以,Edge 變誕生了。Edge是微軟浏覽器的最新版本,它受到了很多好評,但對于 Microsoft 來說,Edge 的出現可能為時已晚。
  • 「IE浏覽器終成時代之淚,Microsoft Edge 成為Windows 11的預設浏覽器」。這是Windows系統更新20年來,IE的首次缺席,也是最後一次。早在Win10更新時微軟就表示,将放棄更新IE轉向開發新的浏覽器Microsoft Edge。如今是徹底要和桌面上的IE說再見了。—— IE 浏覽器将從 Windows 11 中消失,它也将在 2022 年安息。

浏覽器市場佔有率

截止2021年7月初,浏覽器市場佔有率如下所示。

浏覽器使用趨勢變化:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Web_Browser_Usage_Trends

浏覽器市場佔有率:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Web_Browser_Market_Share

國内浏覽器市場佔有率:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Web_Browser_Market_Share_CHN

如果你對以上浏覽器市場佔有率資料有興趣,可以通過以下連結進行檢視:

  • 國内浏覽器市場佔有率
  • 浏覽器市場佔有率[1]
  • 全球浏覽器市場佔有率
  • 全球浏覽器市場佔有率[2]
  • w3counter[3]

浏覽器架構

計算機的核心

三層計算機體系結構:底部是機器硬體,中間是作業系統,頂部是應用程式。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

hw-os-app

當你在電腦或手機上啟動應用時,是 「CPU 和 GPU 為應用供能」。通常情況下應用是通過作業系統提供的機制在 CPU 和 GPU 上運作。

CPU

中央處理器(Central Processing Unit),或簡稱為 CPU。CPU 可以看作是計算機的大腦。「一個 CPU 核心如圖中的辦公人員,可以逐一解決很多不同任務」。它可以在解決從數學到藝術一切任務的同時還知道如何響應客戶要求。過去 CPU 大多是單晶片的。随着現代硬體發展,你經常會有不止一個核心,為你的手機和筆記本電腦提供更多的計算能力。

4 個 CPU 核心作為辦公人員,坐在辦公桌前處理各自的工作:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

CPU

GPU

圖形處理器(Graphics Processing Unit,簡稱為 GPU)是計算機的另一部件。與 CPU 不同,GPU 擅長同時處理跨核心的簡單任務。顧名思義,「它最初是為解決圖形而開發的」。這就是為什麼在圖形環境中“使用 GPU” 或 “GPU 支援”都與快速渲染和順滑互動有關。近年來随着 GPU 加速計算的普及,僅靠 GPU 一己之力也使得越來越多的計算成為可能。

下圖中,許多帶特定扳手的 GPU 核心意味着它們隻能處理有限任務。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

GPU

程序與線程

程序可以被描述為是一個應用的執行程式。線程是位于程序内部并執行其程序程式的任意部分。

啟動應用時會建立一個程序。程式也許會建立一個或多個線程來幫助它工作。作業系統為程序提供了一個可以使用的“一塊”記憶體,所有應用程式狀态都儲存在該私有記憶體空間中。關閉應用程式時,相應的程序也會消失,作業系統會釋放記憶體(下圖中,邊界框為程序,線程作為抽象魚在程序中遊動)。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

memory

程序可以請求作業系統啟動另一個程序來執行不同的任務。此時,記憶體中的不同部分會分給新程序。如果兩個程序需要對話,他們可以通過**程序間通信(IPC)**來進行。許多應用都是這樣設計的,是以如果一個工作程序失去響應,該程序就可以在不停止應用程式不同部分的其他程序運作的情況下重新啟動。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

workerprocess.gif

浏覽器的程序/線程架構模型

浏覽器程序分類

關于如何「建構 web 浏覽器并不存在标準規範」,一個浏覽器的建構方法可能與另一個迥然不同。不同浏覽器的程序/線程架構一般由下圖幾部分:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

browser-arch

Chrome多程序架構

而當下“浏覽器世界的王者” Chrome 架構如下圖所示,渲染程序下顯示了多個層,表明 Chrome 為每個标簽頁運作多個渲染程序。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

browser-arch-chrome

上圖中,頂部是浏覽器程序,它與處理應用其它子產品任務的程序進行協調。對于渲染程序來說,建立了多個渲染程序并配置設定給了每個标簽頁。Chrome 在可能的情況下會給每個标簽頁配置設定一個程序。而現在它試圖給每個站點配置設定一個程序,包括 iframe。

  • 浏覽器程序:控制應用中的 “Chrome” 部分,包括位址欄,書簽,回退與前進按鈕,以及處理 web 浏覽器中網絡請求、檔案通路等不可見的特權部分;
  • 渲染程序:控制标簽頁内網站展示;
  • 插件程序:控制站點使用的任意插件,如 Flash;
  • GPU程序:處理獨立于其它程序的 GPU 任務。GPU 被分成不同程序,因為 GPU 處理來自多個不同應用的請求并繪制在相同表面。

可以簡單了解為不同程序對應浏覽器 UI 的不同部分:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

browserui

「Chrome 更多的是把自己抽象為一個作業系統,網頁或擴充相當于一個個程式」,你甚至可以發現,Chrome 确實自帶了一個任務管理器,在任務管理器面闆會列出目前正在運作的程序以及它們目前的 CPU/記憶體使用量情況等資訊。

一般你可以通過兩種方法打開Chrome任務管理器:

  • 通過在浏覽器頂欄(标簽tab欄)右側右鍵,選擇任務管理器檢視;
  • 點選 Chrome 浏覽器右上角的“選項”菜單(一般是三個點的辨別),選擇“更多工具”子菜單,點選“任務管理器”,打開任務管理器視窗。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

task

前文中提到了 Chrome 使用多個渲染程序,那他有什麼優勢呢?

  • 穩定性:最簡單的情況下,你可以想象每個标簽頁都有自己的渲染程序。假設你打開了三個标簽頁,每個标簽頁都擁有自己獨立的渲染程序。如果某個标簽頁失去響應,你可以關掉這個标簽頁,此時其它标簽頁依然運作着,可以正常使用。如果所有标簽頁都運作在同一程序上,那麼當某個失去響應,所有标簽頁都會失去響應,顯然這樣的體驗會很糟糕。下面是多/單程序架構的對比動圖,供你參考。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

tabs.gif

  • 安全性與沙箱化:把浏覽器工作分成多個程序的另一好處是安全性與沙箱化。由于作業系統提供了限制程序權限的方法,浏覽器就可以用沙箱保護某些特定功能的程序。例如,Chrome 浏覽器可以限制處理使用者輸入(如渲染器)的程序的檔案通路的權限。

由于程序有自己的私有記憶體空間,是以它們通常包含公共基礎設施的拷貝(如Chrome V8引擎)。這意味着使用了更多的記憶體,如果它們是同一程序中的線程,就無法共享這些拷貝(同一個程序中的線程不共享堆棧,堆棧是保證線程獨立運作所必須的)。為了節省記憶體,Chrome 對可啟動的程序數量有所限制。具體限制數值依裝置可提供的記憶體與 CPU 能力而定,但是「當 Chrome 運作時達到限制時,會開始在同一站點的不同标簽頁上運作同一程序」。

Chrome 正在經曆架構變革,它轉變為将浏覽器程式的每一子產品作為一個服務來運作,進而可以輕松實作程序的拆解或聚合。具體表現是,當 Chrome 運作在「強力硬體」上時,它會将每個服務分解到不同程序中,進而「提升穩定性」,但是如果 Chrome 運作在資源有限的裝置上時,它會将服務聚合到一個程序中進而「節省了記憶體占用」。在這一架構變革實作前,類似的整合程序以減少記憶體使用的方法已經在 Android 類平台上使用。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

servicfication.gif

Chrome 67 版本後,桌面版 Chrome 都預設開啟了「站點隔離」,每個标簽頁的 iframe 都有一個單獨的渲染程序。啟用站點隔離是多年來工程人員努力的結果。站點隔離并不隻是配置設定不同的渲染程序這麼簡單。它從根本上改變了 iframe 的通信方式。在一個頁面上打開開發者工具,讓 iframe 在不同的程序上運作,這意味着開發者工具必須在幕後工作,以使它看起來無縫。即使運作一個簡單的 Ctrl + F 來查找頁面中的一個單詞,也意味着在不同的渲染器程序中進行搜尋。你可以看到為什麼「浏覽器工程師把釋出站點隔離功能作為一個重要裡程碑」!

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

isolation

延伸閱讀:Chrome 為什麼多程序而不是多線程?[4]

浏覽器整體架構

如果您是一名前端工程師,那麼,面試時你大機率會被問到過:從 URL 輸入到頁面展現到底發生了什麼?,如果您對這一過程不太熟悉,建議看看下面兩篇文章,在此不過多贅述:

  • 經典面試題:從 URL 輸入到頁面展現到底發生什麼?[5]
  • 在浏覽器輸入 URL 回車之後發生了什麼(超詳細版)[6]

浏覽器的主要任務之一就是渲染展示頁面,不同的浏覽器核心,渲染過程也不完全相同,但大緻流程都差不多,下面這張圖檔是火狐浏覽器(Firefox,可以認為是Netscapede的涅槃重生)開發文檔中的一張圖檔。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

浏覽器架構

上面這張圖檔大體揭示了浏覽器的渲染展示流程,但是從浏覽器的整體架構上來說,上面的圖檔展示的也許隻是浏覽器體系中的冰山一角。

通常意義下,浏覽器架構是如下圖這樣的:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

浏覽器架構

使用者界面

包括位址欄、前進/後退按鈕、書簽菜單等。除了浏覽器主視窗顯示的您請求的頁面外,其他顯示的各個部分都屬于使用者界面。

浏覽器引擎

使用者界面和渲染引擎的橋梁,在使用者界面和渲染引擎之間傳送指令。浏覽器引擎提供了開始加載URL資源 和一些其他進階操作方法,比如:重新加載、前進、後退動作,錯誤資訊、加載進度等。

渲染引擎

負責顯示請求的内容。如果請求的内容是 HTML,它就負責解析 HTML 和 CSS 内容,并将解析後的内容顯示在螢幕上。

所謂浏覽器核心就是指浏覽器最重要或者說核心的部分"Rendering Engine",譯為"渲染引擎"。負責對網頁文法的解析,比如HTML、JavaScript,并渲染到網頁上。是以浏覽器核心也就是浏覽器所采用的渲染引擎,渲染引擎決定這浏覽器如何顯示頁面的内容和頁面的格式資訊。不同的浏覽器核心對文法的解釋也不相同,是以同一網頁在不同核心的浏覽器顯示的效果也會有差異(浏覽器相容)。這也就是網頁開發者在不需要同核心的浏覽器中測試網頁顯示效果的原因。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

RENDERING ENGINE

延伸閱讀:曾紅極一時的紅芯浏覽器,官網對其介紹是:擁有智能的認證引擎、渲染引擎、管控引擎,而且還有強大的“國密通訊協定”,支援統一管控、遠端控制。2018年8月15日,紅芯浏覽器被爆出打開安裝目錄後出現大量和谷歌Chrome浏覽器一緻的同名檔案,其安裝程式的檔案屬性中也顯示了原始檔案名chrome.exe,紅芯浏覽器的官網已撤下了浏覽器的下載下傳連結。8月16日,紅芯聯合創始人高婧回應,紅芯浏覽器“包含‘Chrome’在裡面”,但并非抄襲,而是“站在巨人的肩膀上去做創新”。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

hongxin

言歸正傳,浏覽器核心主要包括以下三個技術分支:排版渲染引擎、 JavaScript引擎,以及其他。

排版引擎:

  • KHTML:KHTML,是HTML網頁排版引擎之一,由KDE所開發。KHTML擁有速度快捷的優點,但對錯誤文法的容忍度則比Mozilla産品所使用的Gecko引擎小。蘋果電腦于2002年采納了KHTML,作為開發Safari浏覽器之用,并釋出所修改的最新及過去版本源代碼。「後來發表的開源WebCore及WebKit引擎,它們均是KHTML的衍生産品」。
  • WebCore:WebCore是「蘋果公司」開發的排版引擎,它是在另外一個排版引擎“KHTML”的基礎上而來的。使用WebCore的主要有Safari浏覽器。

浏覽器的核心引擎,基本上是四分天下:

  • Trident: IE 以Trident 作為核心引擎;
  • Gecko: Firefox 是基于 Gecko 開發;
  • WebKit: 誕生于1998年,并于2005年由Apple公司開源,Safari, Google Chrome,傲遊3,獵豹浏覽器,百度浏覽器 opera浏覽器 基于 Webkit 開發。
  • Presto: Opera的核心,但由于市場選擇問題,主要應用在手機平台--Opera mini。(2013年2月Opera宣布轉向WebKit引擎,2013年4月Opera宣布放棄WEBKIT,跟随GOOGLE的新開發的blink引擎。)

需要略作補充的是,我們經常還會聽到Chromium、Webkit2、Blink這些引擎。

  • Chromium:基于webkit,08年開始作為Chrome的引擎,Chromium浏覽器是Chrome的實驗版,實驗新特性。可以簡單地了解為:Chromium為實驗版,具有衆多新特性;Chrome為穩定版。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

chromium 架構

圖檔來源:萬字詳文:深入了解浏覽器原理[7]
  • Webkit2:2010年随OS X Lion一起面世。WebCore層面實作程序隔離與Google的沙箱設計存在沖突。
  • Blink:基于Webkit2分支,是WebKit中WebCore元件的一個分支,13年谷歌開始作為Chrome 28的引擎內建在Chromium浏覽器裡。Android的WebView同樣基于Webkit2,是現在對新特性支援度最好的核心。Opera(15及往後版本)和Yandex浏覽器中也在使用。
  • 移動端基本上全部是 Webkit 或 Blink 核心(除去 Android 上騰訊家的 X5),這兩個核心對新特性的支援度較高,是以新特性可以在移動端大展身手。

各核心關系圖:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

KHTML

下面我們以WebKit為列,進行簡單介紹,以便讓你對渲染引擎有一個更多的了解。WebKit由多個重要子產品組成,通過下圖我們可以對WebKit有個整體的了解:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

WebKit

WebKit就是一個「頁面渲染以及邏輯處理引擎」,前端工程師把HTML、JavaScript、CSS這“三駕馬車”作為輸入,經過WebKit的處理,就輸出成了我們能看到以及操作的Web頁面。從上圖我們可以看出來,WebKit由圖中框住的四個部分組成。而其中最主要的就是WebCore和JSCore(或者是其它JS引擎)。除此之外,WebKit Embedding API是負責浏覽器UI與WebKit進行互動的部分,而WebKit Ports則是讓Webkit更加友善的移植到各個作業系統、平台上,提供的一些調用Native Library的接口,比如在渲染層面,在iOS系統中,Safari是交給CoreGraphics處理,而在Android系統中,Webkit則是交給Skia。

WebKit的渲染流程:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

WebKit-Rendering

首先浏覽器通過URL定位到了一堆由HTML、CSS、JS組成的資源檔案,通過加載器把資源檔案給WebCore。之後HTML Parser會把HTML解析成DOM樹,CSS Parser會把CSS解析成CSSOM樹。最後把這兩棵樹合并,生成最終需要的渲染樹,再經過布局,與具體WebKit Ports的渲染接口,把渲染樹渲染輸出到螢幕上,成為了最終呈現在使用者面前的Web頁面。

網絡

用于網絡調用,比如 HTTP 請求。其接口與平台無關,并為所有平台提供底層實作,負責網絡通信和安全。

JavaScript 解釋器

用于解析和執行 JavaScript 代碼,執行結果将傳遞給渲染引擎來展示。

使用者界面後端

用于繪制基本的視窗小部件,比如組合框和視窗。其公開了與平台無關的通用接口,而在底層使用作業系統的使用者界面方法。

資料存儲

這是持久層,浏覽器需要在硬碟上儲存各種資料,例如 Cookie。新的 HTML 規範 (HTML5) 定義了“網絡資料庫”,這是一個完整而輕便的浏覽器内資料庫。

求同存異的浏覽器架構

下面列出了部分浏覽器的架構圖,也許有些架構已經改變,有興趣可以簡單參考看看,除了IE之外,大體上各浏覽器的整體架構都是類似的。

Mosaic架構:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Architecture_of_Mosaic

Firefox架構:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Architecture_of_Mozilla

Chrome架構:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Architecture_of_Chrome

Safari架構:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Architecture_of_Safari

IE架構:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

IE架構

浏覽器基本原理

Chrome V8

V8一詞最早見于“V-8 engine”,即V8發動機,一般使用在中高端車輛上。8個氣缸分成兩組,每組4個,成V型排列。是高層次汽車運動中最常見的發動機結構,尤其在美國,IRL,ChampCar和NASCAR都要求使用V8發動機。

關于Chrome V8,筆者曾有一篇筆記做了比較詳細的介紹,全文脈絡如下,感興趣可以參考閱讀[8]。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Chrome-V8

V8是依托Chrome發展起來的,後面确不局限于浏覽器核心。發展至今V8應用于很多場景,例如流行的nodejs,weex,快應用,早期的RN。V8曾經曆過一次比較大的架構調整,主要變化在于“從位元組碼的放棄到真香”。

V8 的早期架構

V8引擎誕生的使命就是要在速度和記憶體回收上進行革命。JavaScriptCore的架構是采用生成位元組碼的方式,然後執行位元組碼。Google覺得JavaScriptCore這套架構不行,生成位元組碼會浪費時間,不如直接生成機器碼快。是以V8在前期的架構設計上是非常激進的,采用了直接編譯成機器碼的方式。後期的實踐證明Google的這套架構速度是有改善,但是同時也造成了「記憶體消耗問題」。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

V8-2010

早期的V8有Full-Codegen和Crankshaft兩個編譯器。V8 首先用 Full-Codegen把所有的代碼都編譯一次,生成對應的機器碼。JS在執行的過程中,V8内置的Profiler篩選出熱點函數并且記錄參數的回報類型,然後交給 Crankshaft 來進行優化。是以Full-Codegen本質上是生成的是未優化的機器碼,而Crankshaft生成的是優化過的機器碼。

随着網頁的複雜化,V8也漸漸的暴露出了自己架構上的缺陷:

  • Full-Codegen 編譯直接生成機器碼,導緻「記憶體占用大」;
  • Full-Codegen 編譯直接生成機器碼,導緻「編譯時間長」,導緻「啟動速度慢」;
  • Crankshaft 無法優化try,catch和finally等關鍵字劃分的代碼塊;
  • Crankshaft 新加文法支援,需要為此編寫适配不同的Cpu架構代碼。

V8 的現有架構

為了解決上述缺點,V8借鑒JavaScriptCore的架構,生成位元組碼。V8采用生成位元組碼的方式後,整體流程如下圖:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

V8-2017

現在的 V8 是一個非常複雜的項目,有超過 100 萬行 C++代碼。它由許多子子產品構成,其中這 4 個子產品是最重要的:

  • Parser[9]:負責将 JavaScript 源碼轉換為 Abstract Syntax Tree (AST)
确切的說,在“Parser”将 JavaScript 源碼轉換為 AST前,還有一個叫”Scanner“的過程,具體流程如下:
  • 【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!
    Scanner
  • Ignition[10]:interpreter,即解釋器,負責将 AST 轉換為 Bytecode,解釋執行 Bytecode;同時收集 TurboFan 優化編譯所需的資訊,比如函數參數的類型;解釋器執行時主要有四個子產品,記憶體中的位元組碼、寄存器、棧、堆。Ignition的原始動機是減少移動裝置上的記憶體消耗。在Ignition之前,V8的Full-codegen基線編譯器生成的代碼通常占據Chrome整體JavaScript堆的近三分之一。這為Web應用程式的實際資料留下了更少的空間。Ignition的位元組碼可以直接用TurboFan生成優化的機器代碼,而不必像Crankshaft那樣從源代碼重新編譯。Ignition的位元組碼在V8中提供了更清晰且更不容易出錯的基線執行模型,簡化了去優化機制,這是V8 自适應優化的關鍵特性。最後,由于生成位元組碼比生成Full-codegen的基線編譯代碼更快,是以激活Ignition通常會改善腳本啟動時間,進而改善網頁加載。
  • TurboFan[11]:compiler,即優化編譯器,利用 Ignition 所收集的類型資訊,将 Bytecode 轉換為優化的彙編代碼;TurboFan項目最初于2013年底啟動,旨在解決Crankshaft的缺點。Crankshaft隻能優化JavaScript語言的子集。例如,它不是設計用于使用結構化異常處理優化JavaScript代碼,即由JavaScript的try,catch和finally關鍵字劃分的代碼塊。很難在Crankshaft中添加對新語言功能的支援,因為這些功能幾乎總是需要為九個支援的平台編寫特定于體系結構的代碼。
  • Orinoco[12]:garbage collector,垃圾回收子產品,負責将程式不再需要的記憶體空間回收。

采用新的Ignition+TurboFan架構後,比Full-codegen+Crankshaft架構記憶體降低一半多,且70%左右的網頁速度得到了提升。

在運作 C、C++以及 Java 等程式之前,需要進行編譯,不能直接執行源碼;但對于 JavaScript 來說,我們可以直接執行源碼(比如:​

​node test.js​

​),它是在運作的時候先編譯再執行,這種方式被稱為「即時編譯(Just-in-time compilation)」,簡稱為 JIT。是以,V8 也屬于 「JIT 編譯器」。

JavaScriptCore

V8未誕生之前,早期主流的JavaScript引擎是JavaScriptCore引擎。JavaScriptCore(以下簡稱JSCore)主要服務于Webkit浏覽器核心,他們都是由蘋果公司開發并開源出來。JSCore是WebKit預設内嵌的JS引擎,之是以說是預設内嵌,是因為很多基于WebKit分支開發的浏覽器引擎都開發了自家的JS引擎,其中最出名的就是前文提到的Chrome的V8。這些「JS引擎的使命都是解釋執行JS腳本」。而在渲染流程上,JS和DOM樹之間存在着互相關聯,這是因為浏覽器中的JS腳本最主要的功能就是操作DOM樹,并與之互動。我們可以通過下圖看下它的工作流程:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

JavaScriptCore

JavaScriptCore主要子產品:「Lexer 詞法分析器,将腳本源碼分解成一系列的Token;Parser 文法分析器,處理Token并生成相應的文法樹;LLInt 低級解釋器,執行Parser生成的二進制代碼;Baseline JIT 基線JIT(just in time 實時編譯);DFG 低延遲優化的JIT;FTL 高通量優化的JIT」。

可以看到,相比靜态編譯語言生成文法樹之後,還需要進行連結,裝載生成可執行檔案等操作,解釋型語言在流程上要簡化很多。這張流程圖右邊畫框的部分就是JSCore的組成部分:Lexer(詞法分析)、Parser(文法分析)、LLInt以及JIT(解釋執行)的部分(之是以JIT的部分是用橙色标注,是因為并不是所有的JSCore中都有JIT部分)。

  • 「詞法分析」很好了解,就是「把一段我們寫的源代碼分解成Token序列的過程」,這一過程也叫「分詞」。在JSCore,詞法分析是由Lexer來完成(有的編譯器或者解釋器把分詞叫做Scanner,比如Chrome v8)。
  • 跟人類語言一樣,我們講話的時候其實是按照約定俗成,交流習慣按照一定的文法講出一個又一個詞語。那類比到計算機語言,計算機要了解一門計算機語言,也要了解一個語句的文法。「Parser會把Lexer分析之後生成的token序列進行文法分析,并生成對應的一棵抽象文法樹(AST)」。之後,ByteCodeGenerator會根據AST來生成JSCore的位元組碼,完成整個「文法解析」步驟。
  • JS源代碼經過了詞法分析和文法分析這兩個步驟,轉成了位元組碼,其實就是經過任何一門程式語言必經的步驟–編譯。但是不同于我們編譯運作OC代碼,JS編譯結束之後,并不會生成存放在記憶體或者硬碟之中的目标代碼或可執行檔案。生成的指令位元組碼,會被立即被JSCore這台虛拟機進行逐行「解釋執行」。運作指令位元組碼(ByteCode)是JS引擎中很核心的部分,各家JS引擎的優化也主要集中于此。
PS:嚴格的講,語言本身并不存在編譯型或者是解釋型,因為語言隻是一些抽象的定義與限制,并不要求具體的實作,執行方式。這裡講JS是一門“解釋型語言”隻是JS一般是被JS引擎動态解釋執行,而并不是語言本身的屬性。

如果對JavaScriptCore有更多興趣,關于JavaScriptCore的更多細節,建議延伸閱讀以下幾篇博文:

  • 深入了解JSCore[13]
  • 深入剖析 JavaScriptCore[14]
  • JavaScriptCore 全面解析[15]
  • 深入淺出 JavaScriptCore[16]

浏覽器與JavaScript

這一小結,還是以Chrome V8為例,簡單闡述浏覽器與JavaScript的關系。

在 「V8 出現之前,所有的 JavaScript 虛拟機所采用的都是解釋執行的方式,這是 JavaScript 執行速度過慢的一個主要原因」。而 V8 率先引入了「即時編譯(JIT)**的**雙輪驅動」的設計(混合使用編譯器和解釋器的技術),這是一種權衡政策,「混合編譯執行和解釋執行這兩種手段」,給 JavaScript 的執行速度帶來了極大的提升。V8 出現之後,各大廠商也都在自己的 JavaScript 虛拟機中引入了 JIT 機制,是以目前市面上 JavaScript 虛拟機都有着類似的架構。另外,「V8 也是早于其他虛拟機引入了惰性編譯、内聯緩存、隐藏類等機制,進一步優化了 JavaScript 代碼的編譯執行效率」。

V8 執行一段 JavaScript 的流程

V8 執行一段 JavaScript 的流程如下圖所示:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

V8執行一段JavaScript流程圖

結合上文介紹的Chrome V8 架構,聚焦到JavaScript上,浏覽器拿到JavaScript源碼,Parser,Ignition 以及 TurboFan 可以将 JS 源碼編譯為彙編代碼,其流程圖如下:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

V8流程

簡單地說,Parser 将 JS 源碼轉換為 AST,然後 Ignition 将 AST 轉換為 Bytecode,最後 TurboFan 将 Bytecode 轉換為經過優化的 Machine Code(實際上是彙編代碼)。

  • 如果函數沒有被調用,則 V8 不會去編譯它。
  • 如果函數隻被調用 1 次,則 Ignition 将其編譯 Bytecode 就直接解釋執行了。TurboFan 不會進行優化編譯,因為它需要 Ignition 收集函數執行時的類型資訊。這就要求函數至少需要執行 1 次,TurboFan 才有可能進行優化編譯。
  • 如果函數被調用多次,則它有可能會被識别為「熱點函數」,且 Ignition 收集的類型資訊證明可以進行優化編譯的話,這時 TurboFan 則會将 Bytecode 編譯為 Optimized Machine Code(已優化的機器碼),以提高代碼的執行性能。

圖檔中的紅色虛線是逆向的,也就是說 Optimized Machine Code 會被還原為 Bytecode,這個過程叫做 「Deoptimization」。這是因為 Ignition 收集的資訊可能是錯誤的,比如 add 函數的參數之前是整數,後來又變成了字元串。生成的 Optimized Machine Code 已經假定 add 函數的參數是整數,那當然是錯誤的,于是需要進行 Deoptimization。

function add(x, y) {
  return x + y;
}

add(1, 2);
add('1', '2');      

「V8 本質上是一個虛拟機」,因為計算機隻能識别二進制指令,是以要讓計算機執行一段進階語言通常有兩種手段:

  • 第一種是将進階代碼轉換為二進制代碼,再讓計算機去執行;
  • 另外一種方式是在計算機安裝一個解釋器,并由解釋器來解釋執行。
  • 解釋執行和編譯執行都有各自的優缺點,「解釋執行啟動速度快,但是執行時速度慢,而編譯執行啟動速度慢,但是執行速度快」。為了充分地利用解釋執行和編譯執行的優點,規避其缺點,「V8 采用了一種權衡政策,在啟動過程中采用了解釋執行的政策,但是如果某段代碼的執行頻率超過一個值,那麼 V8 就會采用優化編譯器将其編譯成執行效率更加高效的機器代碼」。

簡單總結如下,「V8 執行一段 JavaScript 代碼所經曆的主要流程」包括:

  • 初始化基礎環境;
  • 解析源碼生成 AST 和作用域;
  • 依據 AST 和作用域生成位元組碼;
  • 解釋執行位元組碼;
  • 監聽熱點代碼;
  • 優化熱點代碼為二進制的機器代碼;
  • 反優化生成的二進制機器代碼。

Chrome V8 的事件機制

關于異步程式設計和消息隊列,UI 線程提供一個消息隊列,并将待執行的事件添加到消息隊列中,然後 UI 線程會不斷循環地從消息隊列中取出事件、執行事件,通用 UI 線程宏觀架構如下圖所示:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

v8-ui

浏覽器的不同形态

WebView

「WebView 是一種嵌入式浏覽器,原生應用可以用它來展示網絡内容」。WebView 隻是一個「可視化的」元件/控件/微件等。這樣我們可以用它來作為我們原生 app 的視覺部分。當你使用原生應用時,WebView 可能隻是被隐藏在普通的原生 UI 元素中,你甚至用不到注意到它。

如果你把浏覽器想象成兩部分,一部分是 UI(位址欄,導航欄按鈕等),其它部分是把标記跟代碼轉換成我們可見和可互動視圖的引擎。「WebView 就是浏覽器引擎部分」,你可以像插入 iframe 一樣将 Webview 插入到你的原生應用中,并且程式設計化的告訴它将會加載什麼網頁内容。

運作在你的 WebView 中的 JavaScript 有能力調用原生的系統 API。這意味着你不必受到 Web 代碼通常必須遵守的傳統浏覽器安全沙箱的限制。下圖解釋了使用這種技術後的架構差異:

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

webview and webapp

預設情況下,在 WebView 或 Web 浏覽器中運作的任何 Web 代碼都與應用的其餘部分保持隔離。這樣做是出于安全原因,主要是為降低惡意的 JavaScript 代碼對系統造成的傷害。對于任意 Web 内容,這種安全級别很有意義,因為你永遠不能完全信任加載的 Web 内容。但 WebView 的情況并非如此,對于 WebView 方案,開發人員通常可以完全控制加載的内容。惡意代碼進入并在裝置上造成混亂的可能性非常低。

「這就是為什麼對于 WebView,開發人員可以使用各種受支援的方式來覆寫預設的安全行為,并讓 Web 代碼和原生應用代碼互相通信。這種溝通通常稱為 bridge」。你可以在上文的圖檔中看到 bridge 可視化為 Native Bridge 和 JavaScript Bridge 的一部分。

WebView 非常好,雖然它看起來像是完全特殊和獨特的,但請記住,它們隻不過是一個在應用中設定好位置和大小的、沒有任何花哨 UI 的浏覽器,這就是它的精髓。大多數情況下,除非您調用原生 API,否則您不必在 WebView 中專門測試您的 Web 應用程式。此外,您在 WebView 中看到的内容與您在浏覽器中看到的内容相同,尤其是使用同一渲染引擎時:

  • 在 iOS 上,Web 渲染引擎始終是 WebKit,與 Safari 和 Chrome 相同。是的,你沒看錯。iOS 上的 Chrome 實際上使用了 WebKit。
  • 在 Android 上的渲染引擎通常是 Blink,與 Chrome 相同。
  • 在 Windows,Linux 和 macOS 上,由于這些是更寬松的桌面平台,是以在選擇 WebView 風格和渲染引擎時會有很大的靈活性。你看到的流行渲染引擎将是 Blink(Chrome)和 Trident(Internet Explorer),但是沒有一個引擎可以依賴。這完全取決于應用以及它正在使用的 WebView 引擎。

WebView 的應用:

  • WebView 最常見的用途之一是顯示連結的内容;
  • 廣告仍然是原生應用最流行的賺錢方式之一,大多數廣告是通過 WebView 提供的 Web 内容進行投放的;
  • Hybrid Apps,混合應用程式很受歡迎有幾個原因,最大的一個是提高開發人員的生産力。如果你有一個可以在浏覽器中運作的響應式 Web 應用程式,那麼讓相同的應用程式在各種裝置上與混合應用程式一起運作是相當簡單的;當你對 Web 應用進行更新時,所有使用它的裝置都可以立即使用該更改,因為内容來自一個集中的伺服器,而如果是純原生應用,部署和更新時,你将不得不經曆針對每個平台的建構、稽核;
  • 原生應用擴充,如 Microsoft Office 中類似維基百科這樣的基于網絡的擴充就是通過一個 WebView 實作的。

如果你對 WebView 感興趣,可通過以下幾篇文章繼續了解:

  • 7.5.1 WebView(網頁視圖)基本用法[17]
  • Android:這是一份全面 & 詳細的Webview使用攻略[18]

Headless browser

「無頭浏覽器」是一種未配置圖形使用者界面 (GUI) 的 Web 浏覽器,通常通過指令行或網絡通信來執行。它主要由軟體測試工程師使用,沒有 GUI 的浏覽器執行速度更快,因為它們不必繪制視覺内容。無頭浏覽器的最大好處之一是它們能夠在沒有 GUI 支援的伺服器上運作。

Headless 浏覽器對于測試網頁特别有用,因為它們能夠像浏覽器一樣呈現和了解超文本标記語言,包括頁面布局、顔色、字型選擇以及JavaScript和AJAX的執行等樣式元素,這些元素在使用其他測試方法時通常是不可用的。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Headless_architecture

Headless 浏覽器有兩個主要可傳遞成果:

  • 無頭庫,它允許嵌入應用程式控制浏覽器并與網頁互動。
  • 一個無頭外殼,它是一個示例應用程式,用于執行無頭 API 的各種功能。
【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Architecture_of_Puppeteer

Puppeteer 是一個 Node 庫,他提供了一組用來操縱 Chrome 的 API, 通俗來說就是一個 headless chrome 浏覽器 (當然你也可以配置成有 UI 的,預設是沒有的)。既然是浏覽器,那麼我們手工可以在浏覽器上做的事情 Puppeteer 都能勝任, 另外,Puppeteer 翻譯成中文是”木偶”意思,是以聽名字就知道,操縱起來很友善,你可以很友善的操縱她去實作:

1) 生成網頁截圖或者 PDF 2) 進階爬蟲,可以爬取大量異步渲染内容的網頁 3) 實作 UI 自動化測試,模拟鍵盤輸入、表單自動送出、點選、登入網頁等 4) 捕獲站點的時間線,以便追蹤你的網站,幫助分析網站性能問題 5) 模拟不同的裝置 6) ...

Puppeteer 跟 webdriver 以及 PhantomJS 最大的 的不同就是它是站在使用者浏覽的角度,而 webdriver 和 PhantomJS 最初設計就是用來做自動化測試的,是以它是站在機器浏覽的角度來設計的,是以它們 使用的是不同的設計哲學。

  • Headless Chrome architecture[19]
  • puppeteer[20]
  • Puppeteer 入門教程[21]
  • 結合項目來談談 Puppeteer[22]

Electron

Electron(原名為Atom Shell)是 GitHub 開發的一個開源架構。它通過使用 Node.js(作為後端)和Chromium 的渲染引擎(作為前端)完成跨平台的桌面 GUI 應用程式的開發。現已被多個開源 Web 應用程式用于前端與後端的開發,著名項目包括 GitHub 的 Atom 和微軟的 Visual Studio Code。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Electron

Electron Architecture 由多個 Render Process 和一個 Main 程序組成。Main Process 啟動Render Process,它們之間的通信是通過IPC [Inter Process Communication],如下圖所示。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

Electron_Architecture

我們常用的IDE VSCode 就是基于 Electron (原來叫 Atom Shell) 進行開發的。如下圖所示,(點選 VSCode 幫助【Help】 下的 切換開發人員工具即可打開以下面闆)。

【浏覽器】1022- 47 張圖帶你走進浏覽器的世界!

VSCode

VS Code 的其他的主要元件有:

  • 殼:Monaco Editor[23]
  • 核心:Language Server Protocol[24](一個代碼編輯器)
  • Debug Adapter Protocol[25]
  • Xterm.js[26]
延伸閱讀:Electron | Build cross-platform desktop apps with JavaScript, HTML, and CSS[27]

浏覽器代碼相容性測試

  • caniuse[28]
  • browseemall[29]
  • html5test[30]

延伸閱讀

  • 浏覽器簡史[31]
  • Web 浏覽器相關的一些概念[32]
  • 浏覽器的工作原理:新式網絡浏覽器幕後揭秘[33]
  • 從浏覽器多程序到 JS 單線程,JS 運作機制最全面的一次梳理[34]
  • 🤔 移動端 JS 引擎哪家強?美國矽谷找......
  • 從 V8 角度揭秘你不知道的面試八股文
  • 高性能 JavaScript 引擎 V8 - 垃圾回收
  • Inside look at modern web browser[35]【一共四篇,可供參考】

參考資料

  • Inside look at modern web browser[36]
  • 浏覽器是如何工作的:Chrome V8 讓你更懂 JavaScript[37]
  • 深入了解JSCore[38]
  • The Story of the Web: A History Of Internet Browsers[39]
  • PPT - Browser Architecture[40]
  • JavaScript 引擎 V8 執行流程概述[41]
  • Understanding WebViews[42]
  • Quantum Up Close: What is a browser engine?[43]

本文首發于個人部落格[44],歡迎指正和star[45]。