https://juejin.im/post/5e572a34518825490f722b9ehtml
博客
歡迎關注本人博客:github.com/LuckyWinty/…前端
背景
前段時間梳理了一下瀏覽器相關的知識,還作了一個公司級的分享,60多人過來聽了個人分享,感受還行,哈哈。先看一下分享目錄:html5
![GitHub](http://static.javashuo.com/static/loading.gif)
本篇文章,若是直接貼ppt圖,理解起來可能比較費勁,這裏就大概講一下內容,再附上我以前已經把部份內容輸出了完整的文章的連接,方便你們結合ppt來理解,所以本文結合ppt食用效果更佳哦~git
PS:公衆號後臺回覆 瀏覽器 便可獲取本次分享的完整ppt github
Chrome 基本架構介紹
總體架構
瀏覽器的主要功能就是向服務器發出請求,在瀏覽器窗口中展現您選擇的網絡資源,這裏所說的資源通常是指 HTML 文檔,也能夠是 PDF、圖片或其餘的類型。大致上,瀏覽器能夠分爲五部分: web
![GitHub](http://static.javashuo.com/static/loading.gif)
-
用戶界面,主要負責展現頁面中,除了 page 自己的內容,咱們能夠粗略地理解爲打開一個空頁面的時候呈現的界面就是瀏覽器的用戶界面(GUI)。面試
-
瀏覽器引擎,這裏我的認爲主要指的是在用戶界面和渲染引擎之間傳遞指令,以及調度瀏覽器各方面的資源,協調爲呈現頁面、完成用戶指令而工做。算法
-
呈現引擎,按圖中看,包含了一個 compositor(合成器)和 Javascript Engine(JS解釋引擎)。分別是負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在屏幕上 和 用於解析和執行 JavaScript 代碼。chrome
-
後端服務層,這裏包含了一些後端服務。好比網絡請求層(network)、數據存儲,瀏覽器須要在硬盤上保存各類數據,例如 Cookie、Storage等。後端
-
特別服務層,這裏主要指的是一些瀏覽器自帶的服務,好比你填完某個網站的帳號密碼,瀏覽器能夠幫你記住帳號密碼,又好比開啓瀏覽器的暗黑模式等特殊的服務。
以上,對前端來講,比較重要的是渲染引擎(一些文章也叫瀏覽器引擎)。咱們能夠看看都有哪些渲染引擎的內核。
![GitHub](http://static.javashuo.com/static/loading.gif)
多進程架構
早期的web瀏覽器是單線程的,發生⻚⾯⾏爲不當、瀏覽器錯誤、瀏覽器插件等錯誤都會引發整個瀏覽器或當前運 ⾏的選項卡關閉。所以Chrome將chromium應⽤程序放在相互隔離的獨⽴的進程,也就是多進程的一個架構。
![GitHub](http://static.javashuo.com/static/loading.gif)
多進程的優點有:
- 防⼀個⻚⾯崩潰影響整個瀏覽器
- 安全性和沙盒,因爲操做系統提供了限制進程權限的方法,所以瀏覽器能夠從某些功能中,對某些進程進行沙箱處理。例如,Chrome 瀏覽器能夠對處理用戶輸入(如渲染器)的進程,限制其文件訪問的權限。
- 進程有⾃⼰的私有內存空間,能夠擁有更多的內存。
多進程的劣勢有:
- 給每一個進程分配了單獨的內存,儘管Chrome自己有一些優化策略,好比爲了節省內存,Chrome限制了它能夠啓動的進程數量。限制因設備的內存和CPU功率⽽異,但當Chrome達到限制時,它會在⼀個進程中開始從同⼀站點運⾏多個選項卡。
- 有更高的資源佔用。由於每一個進程都會包含公共基礎結構的副本(如 JavaScript 運行環境),這就意味着瀏覽器會消耗更多的內存資源。
多進程的架構,還有優化的地方,所以 Chrome 將來的架構是一個面向服務的架構,將瀏覽器程序的每一個部分,做爲一項服務運行,從而能夠輕鬆拆分爲不一樣的流程或彙總爲同一個流程。這樣能夠作到,當 Chrome 在強大的硬件上運行時,它可能會將每一個服務拆分爲不一樣的進程,從而提供更高的穩定性,但若是它位於資源約束的設備上,Chrome 會將服務整合到一個進程中,從而整合流程以減小內存使用。
關於架構這章,更詳細的內容能夠看我這篇文章,《一文看懂現代瀏覽器架構》
瀏覽器中頁面渲染過程
按照渲染的時間順序,流水線可分爲以下幾個子階段:構建 DOM 樹、樣式計算、佈局階段、分層、柵格化和顯示。如圖:
![GitHub](http://static.javashuo.com/static/loading.gif)
- 渲染進程將 HTML 內容轉換爲可以讀懂DOM 樹結構。
- 渲染引擎將 CSS 樣式錶轉化爲瀏覽器能夠理解的styleSheets,計算出 DOM 節點的樣式。
- 建立佈局樹,並計算元素的佈局信息。
- 對佈局樹進行分層,並生成分層樹。
- 爲每一個圖層生成繪製列表,並將其提交到合成線程。合成線程將圖層分圖塊,並柵格化將圖塊轉換成位圖。
- 合成線程發送繪製圖塊命令給瀏覽器進程。瀏覽器進程根據指令生成頁面,並顯示到顯示器上。
構建 DOM 樹
瀏覽器從網絡或硬盤中得到HTML字節數據後會通過一個流程將字節解析爲DOM樹,先將HTML的原始字節數據轉換爲文件指定編碼的字符,而後瀏覽器會根據HTML規範來將字符串轉換成各類令牌標籤,如html、body等。最終解析成一個樹狀的對象模型,就是dom樹。
![GitHub](http://static.javashuo.com/static/loading.gif)
具體步驟:
- 轉碼(Bytes -> Characters)—— 讀取接收到的 HTML 二進制數據,按指定編碼格式將字節轉換爲 HTML 字符串
- Tokens 化(Characters -> Tokens)—— 解析 HTML,將 HTML 字符串轉換爲結構清晰的 Tokens,每一個 Token 都有特殊的含義同時有本身的一套規則
- 構建 Nodes(Tokens -> Nodes)—— 每一個 Node 都添加特定的屬性(或屬性訪問器),經過指針可以肯定 Node 的父、子、兄弟關係和所屬 treeScope(例如:iframe 的 treeScope 與外層頁面的 treeScope 不一樣)
- 構建 DOM 樹(Nodes -> DOM Tree)—— 最重要的工做是創建起每一個結點的父子兄弟關係
樣式計算
渲染引擎將 CSS 樣式錶轉化爲瀏覽器能夠理解的 styleSheets,計算出 DOM 節點的樣式。
CSS 樣式來源主要有 3 種,分別是經過 link 引用的外部 CSS 文件、style標籤內的 CSS、元素的 style 屬性內嵌的 CSS。
,其樣式計算過程主要爲:
![GitHub](http://static.javashuo.com/static/loading.gif)
頁面佈局
佈局過程,即排除 script、meta
等功能化、非視覺節點,排除 display: none
的節點,計算元素的位置信息,肯定元素的位置,構建一棵只包含可見元素佈局樹。如圖:
![GitHub](http://static.javashuo.com/static/loading.gif)
迴流和重繪
,關於迴流和重繪,詳細的能夠看我另外一篇文章
《瀏覽器相關原理(面試題)詳細總結二》,這裏就不說了~
生成分層樹
頁面中有不少複雜的效果,如一些複雜的 3D 變換、頁面滾動,或者使用 z-indexing 作 z 軸排序等,爲了更加方便地實現這些效果,渲染引擎還須要爲特定的節點生成專用的圖層,並生成一棵對應的圖層樹(LayerTree),如圖:
![GitHub](http://static.javashuo.com/static/loading.gif)
並非佈局樹的每一個節點都包含一個圖層,若是一個節點沒有對應的層,那麼這個節點就從屬於父節點的圖層。那麼須要知足什麼條件,渲染引擎纔會爲特定的節點建立新的層呢?詳細的能夠看我另外一篇文章《瀏覽器相關原理(面試題)詳細總結二》,這裏就不說了~
柵格化
合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操做是由柵格化來執行的。所謂柵格化,是指將圖塊轉換爲位圖。如圖:
![GitHub](http://static.javashuo.com/static/loading.gif)
一般一個頁面可能很大,可是用戶只能看到其中的一部分,咱們把用戶能夠看到的這個部分叫作視口(viewport)。在有些狀況下,有的圖層能夠很大,好比有的頁面你使用滾動條要滾動很久才能滾動到底部,可是經過視口,用戶只能看到頁面的很小一部分,因此在這種狀況下,要繪製出全部圖層內容的話,就會產生太大的開銷,並且也沒有必要。
顯示
最後,合成線程發送繪製圖塊命令給瀏覽器進程。瀏覽器進程根據指令生成頁面,並顯示到顯示器上,渲染過程完成。
瀏覽器中的JavaScript運行機制
JavaScript如何工做的,首先要理解幾個概念,分別是JS Engine(JS引擎)、Context(執行上下文)、Call Stack(調用棧)、Event Loop(事件循環)。
JS Engine(JS引擎)
JavaScript引擎就是用來執行JS代碼的, 經過編譯器將代碼編譯成可執行的機器碼讓計算機去執行。目前比較流行的就是V8引擎,Chrome瀏覽器和Node.js採用的引擎就是V8引擎。 引擎主要由堆(Memory Heap)和棧(Call Stack)組成。
- Heap(堆) - JS引擎中給對象分配的內存空間是放在堆中的
- Stack(棧)- 這裏存儲着JavaScript正在執行的任務。每一個任務被稱爲幀(stack of frames)
Context(執行上下文)
執行上下文是 JavaScript 執行一段代碼時的運行環境,好比調用一個函數,就會進入這個函數的執行上下文,肯定該函數在執行期間用到的諸如 this、變量、對象以及函數等。
JavaScript 中有三種執行上下文類型。
全局執行上下文
— 這是默認或者說基礎的上下文,任何不在函數內部的代碼都在全局上下文中。它會執行兩件事:建立一個全局的 window 對象(瀏覽器的狀況下),而且設置 this 的值等於這個全局對象。一個程序中只會有一個全局執行上下文。函數執行上下文
— 每當一個函數被調用時, 都會爲該函數建立一個新的上下文。每一個函數都有它本身的執行上下文,不過是在函數被調用時建立的。函數上下文能夠有任意多個。每當一個新的執行上下文被建立,它會按定義的順序(將在後文討論)執行一系列步驟。Eval 函數執行上下文
— 執行在 eval 函數內部的代碼也會有它屬於本身的執行上下文,但因爲 JavaScript 開發者並不常用 eval,因此在這裏我不會討論它。
建立執行上下文有兩個階段:1) 編輯(建立)階段 和 2) 執行階段。舉個例子:
![GitHub](http://static.javashuo.com/static/loading.gif)
Call Stack(調用棧)
JavaScript 引擎正是利用棧的這種結構來管理執行上下文的。在執行上下文建立好後,JavaScript 引擎會將執行上下文壓入棧中,一般把這種用來管理執行上下文的棧稱爲執行上下文棧,又稱調用棧。
瀏覽器中查看調用棧的方法:
- 當你執行一段複雜的代碼時,你可能很難從代碼文件中分析其調用關係,這時候你能夠在你想要查看的函數中加入斷點,而後當執行到該函數時,就能夠查看該函數的調用棧了。
- console.trace()
調用棧是有大小的,當入棧的執行上下文超過必定數目,JavaScript 引擎就會報錯,咱們把這種錯誤叫作棧溢出。正常業務需求通常不會發生棧溢出的錯誤,只有遞歸忘記寫邊界的時候會出現棧溢出,咱們寫代碼的時候要注意一下。
Event Loop(事件循環)
JavaScript代碼的執行過程當中,除了依靠函數調用棧來搞定函數的執行順序外,還依靠任務隊列(task queue)來搞定另一些代碼的執行。整個執行過程,咱們成爲事件循環過程。一個線程中,事件循環是惟一的,可是任務隊列能夠擁有多個。任務隊列又分爲macro-task(宏任務)與micro-task(微任務),在最新標準中,它們被分別稱爲task與jobs。
macro-task大概包括:
- script(總體代碼)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
micro-task大概包括:
- process.nextTick
- Promise
- Async/Await(實際就是promise)
- MutationObserver(html5新特性)
總體執行,我畫了一個流程圖:
![GitHub](http://static.javashuo.com/static/loading.gif)
總的結論就是,執行宏任務,而後執行該宏任務產生的微任務,若微任務在執行過程當中產生了新的微任務,則繼續執行微任務,微任務執行完畢後,再回到宏任務中進行下一輪循環。舉個例子:
![GitHub](http://static.javashuo.com/static/loading.gif)
結合流程圖理解,答案輸出爲:async2 end => Promise => async1 end => promise1 => promise2 => setTimeout
垃圾回收與內存泄露
一般狀況下,垃圾數據回收分爲手動回收和自動回收兩種策略。
- 手動回收策略,什麼時候分配內存、什麼時候銷燬內存都是由代碼控制的。
- 自動回收策略,產生的垃圾數據是由垃圾回收器來釋放的,並不須要手動經過代碼來釋放。
V8 中會把堆分爲新生代和老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放的生存時間久的對象。
新生代算法
新生代中用Scavenge 算法來處理,把新生代空間對半劃分爲兩個區域,一半是對象區域,一半是空閒區域。新加入的對象都會存放到對象區域,當對象區域快被寫滿時,就須要執行一次垃圾清理操做。
![GitHub](http://static.javashuo.com/static/loading.gif)
爲了執行效率,通常新生區的空間會被設置得比較小,也正是由於新生區的空間不大,因此很容易被存活的對象裝滿整個區域。爲了解決這個問題,JavaScript 引擎採用了對象晉升策略,也就是通過兩次垃圾回收依然還存活的對象,會被移動到老生區中。
老生代算法
老生代中用 標記 - 清除(Mark-Sweep)和 標記 - 整理(Mark-Compact)的算法來處理。標記階段就是從一組根元素開始,遞歸遍歷這組根元素(遍歷調用棧),能到達的元素稱爲活動對象,沒有到達的元素就能夠判斷爲垃圾數據.而後在遍歷過程當中標記,標記完成後就進行清除過程。
![GitHub](http://static.javashuo.com/static/loading.gif)
算法比較
![GitHub](http://static.javashuo.com/static/loading.gif)
內存泄露
再也不用到的內存,沒有及時釋放,就叫作內存泄漏(memory leak)。泄露的緣由主要有緩存、閉包、全局變量、計時器中引用沒有清除等緣由。
這裏我寫了一篇更詳細具體的文章,《Chrome 瀏覽器垃圾回收機制與內存泄漏分析》。
你們能夠看一下,這裏就不詳細說了~
利用瀏覽器進行性能分析
這部分的內容,比較重要。我用了2篇文章來詳細說了。
你們能夠看一下,這裏就不詳細說了~
ppt獲取
公衆號後臺回覆 瀏覽器 便可獲取本次分享的完整ppt
參考資料
- 極客時間《瀏覽器工做原理與實踐》
- Chrome開發者文檔,developers.google.com/web/tools/c…
- JavaScript運行機制深刻淺出學習,zhuanlan.zhihu.com/p/33125763
- Winty blog,github.com/LuckyWinty/…
最後
- 歡迎加我微信(winty230),拉你進技術羣,長期交流學習...
- 歡迎關注「前端Q」,認真學前端,作個有專業的技術人...
![GitHub](http://static.javashuo.com/static/loading.gif)