瀏覽器渲染原理(一)

一開始我只想弄明白js在瀏覽器裏面究竟是怎麼執行的,發現本身須要補補基礎,因而打算總結一個文章來補一下基礎,有什麼不對的地方還請大佬們指正。css

這篇文章大算分幾個章節總結一下本身學到的知識點,順便複習一些計算機基礎知識,瞭解JS代碼在瀏覽器是如何執行的,如何渲染頁面的。html

進程和線程

-進程是一個工廠,工廠有它本身獨立的資源,工廠之間相互獨立;前端

-線程是工廠中的工人,多個工人協做完成工廠的任務;git

-工廠內能夠有一個或者多個工人;github

-工人之間共享空間;瀏覽器

而後咱們再看下圖,稍微再解釋一下。markdown


首先計算機能夠有不少應用程序,瀏覽器就是其中之一。瀏覽器能夠有不少個模塊(進程),每一個模塊又獨立且分配有本身的資源(線程)。網絡

1.應用程序是有一個或多個模塊組成的,以如今的谷歌瀏覽器舉例,他有一個瀏覽器主進程、一個GPU進程、一個網絡進程、多個渲染進程和多個插件進程數據結構

2.進程是由一個或者多個線程組成的,線程是進程的基本單位多線程

3.進程之間互相獨立,有本身的資源,好比CPU的時間片,佔用的內存。

4.一個進程下的線程(工人)共享進程(工廠)的資源,包括代碼段數據段內存等

5.線程之間協做在進程中完成任務。

6.進程是CPU資源分配的最小單位,是能夠擁有資源和獨立運行的最小單位。

7.線程是CPU調度的最小單位,由於進程能夠有一個或者多個線程。

瀏覽器是多進程的

瀏覽器是多進程的,有一個主控進程,以及每個tab頁面都會新開一個進程(某些狀況下多個tab會合並進程,例如多個空白頁等特殊狀況);以如今主流的谷歌瀏覽器來舉例,它主要包括如下幾個進程

1.瀏覽器主進程:瀏覽器主進程,負責協調主控,只有一個

  • 負責瀏覽器界面顯示,於用戶交換,前進後退等
  • 負責各個界面的管理,建立/銷燬其它進程
  • 將Renderer進程獲得的內存中的Bitmap,繪製到用戶界面上
  • 網絡資源管理,下載等

2.GPU進程:最多一個,用於3D繪製

3.插件進程:每使用一類插件都會建立一個進程

4.瀏覽器渲染進程(內核):默認每打開一個tab頁,都會新建立一個。互不影響,控制頁面渲染,腳本執行,事件處理等等。(有時候會優化,如多個空白頁tab會合成一個)


tips:如圖,打開任務管理器,打開三個tab頁,就會建立三個進程。負責控制渲染本身的頁面內容。那爲何瀏覽器是多進程的?你下下,若是是單進程,某個tab頁卡死或者崩潰了,就會影響到其它tab頁

瀏覽器渲染進程(內核)

對於瀏覽器應用的這些進程,咱們只須要知道只有一個瀏覽器主進程,多個協做進程(GPU、渲染進程、網絡進程、插件進程等等)。前端只須要重點關注渲染進程就能夠了。由於渲染進程是渲染頁面的。而後這個渲染進程,它是多線程的,若是還不明白進程和線程的關係,聯想一下工廠和工廠的關係。回頭繼續看圖理解一下進程和線程的關係。


GUI渲染線程

GUI渲染線程主要負責渲染頁面的,解析HTML、CSS,構建成一個DOM樹和render樹;當界面須要重繪或者重排引起迴流時,這個線程會執行。注意,GUI渲染線程和JS引擎執行是互斥的,當js引擎執行時,GUI線程會被掛起,保存到一個任務隊列中,等到JS引擎線程空閒時候纔會出隊被當即執行。若是不互斥,你一邊用戶操做一遍渲染界面,會致使渲染不許確等,這就決定了JS是單線程的了。總結爲如下幾點:

  • 渲染界面,解析html、css、構建dom樹和renderObject樹,佈局和繪製等。
  • 當界面須要重繪或者某種操做須要迴流的時候,都會執行這個線程
  • GUI線程和JS引擎線程是互斥的

JS引擎線程

負責處理js腳本,解析腳本運行代碼。可是js引擎會一直等待着任務隊列的到來,而後加以處理。一樣,GUI渲染線程和JS引擎執行是互斥的,若是js執行的時候太長,就會致使頁面渲染不連貫,也就是阻塞頁面,致使了頁面渲染加載被阻塞了,這就是爲何經常不把script標籤放在頭部而放在body底部下的緣由。總結爲如下幾點:

  • 也稱爲JS內核,負責處理Javascript腳本程序。(例如V8引擎)
  • JS引擎線程負責解析運行Javascript腳本代碼。
  • JS引擎會一直等待着任務隊列中任務的到來,而後加以處理,一個Tab頁(渲染進程)中不管何時都只有一個JS線程在運行JS程序
  • 同理,GUI線程和JS引擎是執行時互斥的

事件觸發線程(Event lop)

這個事件觸發線程是歸屬瀏覽器的,而不是JS引擎的,能夠這麼理解,由於JS引擎是單線程的,它很忙,必需要有協助線程協助它完成一些事件,而這些事情,就是事件循環。

當JS引擎執行到一些網絡請求,setTimeOut等異步的代碼塊時,會將這些異步的任務加入到事件線程中。當這些任務符合處理條件的時候,該線程會把任務加到待處理隊列的隊尾。等待JS引擎執行。(JS引擎爲空時候才執行這個隊列的任務)總結爲如下幾點:

  • 該線程是歸屬瀏覽器的,不是JS引擎的,用來控制事件循環機制
  • 當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其餘線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
  • 當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
  • 因爲JS是單線程的,全部這些事件都得排隊等待JS引擎執行。(引擎空閒時出隊執行)

定時觸發器線程

傳說中的 setInterval setTimeout 所在線程,瀏覽器定時器不是由JS引擎計時的,你想呀,由於JS引擎是單線程的,若是執行這些計算器,阻塞頁面徹底中止了,用戶就不用點網頁了。所以經過單獨線程來計時並觸發定時,計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行。總結爲如下幾點:

  • setInterval 和 setTimeout所計時的線程
  • 計時完畢後,會加入事件隊列中,等待JS引擎執行。(JS引擎空閒時執行)
  • W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。

異步http請求線程

在 XMLHttpRequest 在鏈接後是經過瀏覽器新開一個線程請求,當檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件,將這個回調再放入事件隊列中。再由JS引擎執行。

  • 在XMLHttpRequest在鏈接後是經過瀏覽器新開一個線程請求
  • 將檢測到狀態變動時,若是設置有回調函數,異步線程就產生狀態變動事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。

tips:到這裏,你應該知道了瀏覽器打開一個頁面顯示出來的全過程了,若是不清楚,須要再回頭看看。上面總提到一個事件隊列,它是基於哪一個線程的呢?答案是事件觸發線程

瀏覽器主進程和其它進程的通訊

browser主進程是一開始說瀏覽器的主進程,是控制協調其它進程的主進程。在打開一個Tab頁的時候,就會建立一個主線程。經過主線程協調其它線程完成頁面渲染。這裏會涉及一些併發並行的操做系統知道點,就不展開擴展了,同時簡單回顧下以前的整個流程。


  • Browser主進程收到用戶請求後,首先須要獲取頁面內容(譬如經過網絡進程下載資源),隨後將該任務經過RendererHost接口傳遞給資源Render進程
  • Renderer進程的Renderer接口收到消息,簡單解釋後,交給渲染線程,而後開始渲染

    • 渲染線程接收請求,加載網頁並渲染網頁,這其中可能須要Browser主進程獲取資源和須要GPU進程來幫助渲染,這期間都是主進程調度協助進程完成的。
    • 固然可能會有JS線程操做DOM(這樣可能會形成迴流/重繪)
    • 最後Render進程將結果傳遞給Browser進程
  • Browser主進程接收到每一個協做進程處理好的結果後,將結果繪製出來
tips:這一塊流程若是深挖的話,幾十篇文章都寫不完,前端了解整個流程的協做運做就能夠了

梳理瀏覽器渲染線程之間的關係

渲染進程,即瀏覽器內核,纔是咱們前端關注的重點,再次強調一下。而後咱們必須知道一下幾點。

  1. GUI線程和JS引擎線程是互斥的
  2. 由於第1點,若是JS引擎執行時間好久,就會引起頁面阻塞
  3. HTML5中支持了Web Worker,以解決頁面阻塞,可是Web Worker是JS引擎子線程,受JS引擎控制,JS單線程依舊不會改變

瀏覽器渲染流程

這個過程也很複雜,能夠參考我以前的計算機網絡(前端版)的問題,過多的細節不在這裏描述。這裏只作簡單的描述

1.瀏覽器輸入URL,瀏覽器主線程接管,開一個下載線程,已進行http請求。(忽略DNS等等)

2.拿到響應內容,將內容經過RendererHost接口轉交給Renderer渲染進程

3.瀏覽器開始渲染(可能會協調GPU等線程協做完成)

渲染進程(內核)拿到內容後,分如下幾步開始渲染:

1.解析HTML創建dom樹

2.解析CSS構建render樹,其實就是講css解析成樹的數據結構,而後結合dom樹造成render樹。

3.佈局render樹(Layout/reflow),複雜計算元素的大小、位置等信息

4.開始繪製render樹(paint),繪製頁面像素信息

5.瀏覽器會將繪製信息發送給GPU,GPU會將合成(composite),顯示在屏幕上。

步驟並不詳細,只是列個大概,更詳細的請參考別的渲染文章,這裏不進行深究。

tips:這也是爲何頁面渲染至少會觸發一次迴流的緣由。

這裏參考一張圖來梳理一下上面的步驟:


注意細節

  1. DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片。有async的腳本也不必定完成。
  2. 當 onload 事件觸發時,頁面上全部的DOM,樣式表,腳本,圖片都已經加載完成了。頁面渲染完畢。
  3. css加載不會阻塞DOM樹解析(異步加載時DOM照常構建);但會阻塞render樹渲染(渲染時需等css加載完畢,由於render樹須要css信息)
  4. 渲染步驟中就提到了composite的概念,能夠簡單的理解爲,瀏覽器渲染圖層通常包括兩大類,就是普通圖層複合圖層。
  5. 文檔就能夠當作一個默認的複合圖層,其次absolute這樣的脫離文檔流也依然屬於複合圖層。硬件加速什麼的這裏就不展開描述了。

談談JS的運行機制(Event Loop)

到此時,頁面已是完成了初次渲染。

event lop其實就渲染進程裏面的事件觸發線程(線程裏維護的隊列),若是不記得了往上翻圖回憶一下。它其實就是和JS引擎線程協做完成任務的。(界面的一些交換)

先從新溫習一下,瀏覽器渲染進程的三個線程:

  • JS引擎線程
  • 事件觸發線程

  • 定時觸發線程

而後再提JS引擎主執行棧幾個概念:

1.JS分同步任務和異步任務

2.同步任務都在主線程上執行,造成一個主執行棧

3.主線程以外,事件觸發線程管理着一個任務隊列(就是上文屢次提到的任務隊列),只要異步任務有了運行結果,就在任務隊列之中入隊一個事件

4.一旦主執行棧中的全部同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧中,開始執行,以此循環。


tips:看到這裏,應該就能夠理解爲何有時候setTimeout推入的事件不能準時執行?由於可能在它推入到事件列表時,主線程還不空閒,正在執行其它代碼, 因此天然有偏差。

總結

瀏覽器原理仍是很複雜的,瞭解它的渲染原理,能夠很好的幫助咱們優化性能。對於像Event lop事件循環機制、渲染原理等等具體的細節。能夠參考大佬們的文章,我這裏只作一個小擴展。

參考文章:

yuchengkai.cn/docs/fronte…

sanyuan0704.github.io/frontend_da…

相關文章
相關標籤/搜索