瀏覽器的渲染機制-入門詳細圖解

瀏覽器多進程到JS單線程,將JS引擎的運行機制系統的梳理一遍,瞭解整個知識體系,能夠幫助我們更好的進行前端開發。

參考網址:https://juejin.im/post/5a6547d0f265da3e283a1df7

一、區分進程和線程

可以打開電腦的任務管理器查看

具像化描述

  • 進程是一個工廠,工廠有它的獨立資源
  • 工廠之間相互獨立

  • 線程是工廠中的工人,多個工人協作完成任務
  • 工廠內有一個或多個工人
  • 工人之間共享空間

  • 工廠的資源 -> 系統分配的內存(獨立的一塊內存)
  • 工廠之間的相互獨立 -> 進程之間相互獨立

  • 多個工人協作完成任務 -> 多個線程在進程中協作完成任務
  • 工廠內有一個或多個工人 -> 一個進程由一個或多個線程組成
  • 工人之間共享空間 -> 同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)

總結

  • 進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
  • 線程是cpu調度的最小單位(線程是建立在進程的基礎上的一次程序運行單位,一個進程中可以有多個線程)

二、瀏覽器是多進程的

  • 瀏覽器是多進程的
  • 瀏覽器之所以能夠運行,是因爲系統給它的進程分配了資源(cpu、內存)
  • 簡單點理解,每打開一個Tab頁,就相當於創建了一個獨立的瀏覽器進程。

A、瀏覽器是包含哪些主要進程?

  • Browser進程:瀏覽器的主進程(負責協調、主控),只有一個。作用有
    1、負責瀏覽器界面顯示,與用戶交互。如前進,後退等
    2、負責各個頁面的管理,創建和銷燬其他進程
    3、將Renderer進程得到的內存中的Bitmap,繪製到用戶界面上
    4、網絡資源的管理,下載等
  • 第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建
  • GPU進程:最多一個,用於3D繪製等
  • 瀏覽器渲染進程(瀏覽器內核)(Renderer進程,內部是多線程的):默認每個Tab頁面一個進程,互不影響。主要作用爲:頁面渲染,腳本執行,事件處理等

B、重點是-瀏覽器渲染進程(瀏覽器內核)

瀏覽器的渲染進程是多線程的,主要包括:

  1. GUI渲染線程(和js引擎線程互斥,只能執行一個)
    • 負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。
    • 當界面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該線程就會執行
    • 注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起(相當於被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閒時立即被執行。
  2. JS引擎線程(核心線程)
    • 也稱爲JS內核,負責處理Javascript腳本程序。(例如V8引擎)
    • JS引擎線程負責解析Javascript腳本,運行代碼。
    • JS引擎一直等待着任務隊列中任務的到來,然後加以處理,一個Tab頁(renderer進程)中無論什麼時候都只有一個JS線程在運行JS程序
    • 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
  3. 事件觸發線程(事件觸發後,放置隊尾等js引擎線程處理)
    • 歸屬於瀏覽器而不是JS引擎,用來控制事件循環(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協助)
    • 當JS引擎執行代碼塊如setTimeOut時(也可來自瀏覽器內核的其他線程,如鼠標點擊、AJAX異步請求等),會將對應任務添加到事件線程中
    • 當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
    • 注意,由於JS的單線程關係,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閒時纔會去執行)
  4. 定時觸發器線程(計時完成後,等js引擎線程處理)
    • 傳說中的setInterval與setTimeout所在線程
    • 瀏覽器定時計數器並不是由JavaScript引擎計數的,(因爲JavaScript引擎是單線程的, 如果處於阻塞線程狀態就會影響記計時的準確)
    • 因此通過單獨線程來計時並觸發定時(計時完畢後,添加到事件隊列中,等待JS引擎空閒後執行)
    • 注意,W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。
  5. 異步http請求線程(有狀態變更時,放置隊列待JS引擎線程處理)
    • 在XMLHttpRequest在連接後是通過瀏覽器新開一個線程請求
    • 將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由JavaScript引擎執行。

C、Brower進程和瀏覽器內核(Renderer進程)(包括上述那一堆線程的那個進程)的通信過程

自己總結了一張圖
自己總結了一張圖

三、簡單梳理瀏覽器渲染流程(簡單版本)

前提工作簡略如下(可自行學習http請求那一類)

這裏有一篇原作者寫的,從輸入url到頁面加載的過程整個知識體系的文章,非常值得一看:
https://juejin.im/post/5aa5cb846fb9a028e25d2fb1

  • 瀏覽器輸入url,瀏覽器主進程接管,開一個下載線程,
    然後進行 http請求(略去DNS查詢,IP尋址等等操作),然後等待響應,獲取內容,
    隨後將內容通過RendererHost接口轉交給Renderer進程
  • 瀏覽器渲染流程開始

A、瀏覽器器內核拿到內容後,渲染大概可以劃分成以下幾個步驟:

  1. 解析html建立dom樹
  2. 解析css構建render樹(將CSS代碼解析成樹形的數據結構,然後結合DOM合併成render樹)
  3. 佈局render樹(Layout/reflow),負責各元素尺寸、位置的計算
  4. 繪製render樹(paint),繪製頁面像素信息
  5. 瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上。

渲染完畢後就是load事件了,之後就是自己的JS邏輯處理了
在這裏插入圖片描述

load事件與DOMContentLoaded事件的先後

DOMContentLoaded -> load

css加載是否會阻塞dom樹渲染?

  • css是由單獨的下載線程異步下載的
  • css加載不會阻塞DOM樹解析(異步加載時DOM照常構建)
  • 但會阻塞render樹渲染(渲染時需等css加載完畢,因爲render樹需要css信息)

B、普通圖層和複合圖層

(TODO:不太清楚,建議後續再鞏固)

四、從Event Loop談JS的運行機制

(即上述瀏覽器內核內各個線程是如何協同運作的)
在這裏插入圖片描述
在這裏插入圖片描述
1、JS分爲同步任務和異步任務
2、同步任務都在主線程上執行,形成一個執行棧
3、主線程之外,事件觸發線程管理着一個任務隊列,只要異步任務有了運行結果,就在任務隊列之中放置一個事件。
4、一旦執行棧中的所有同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列,將可運行的異步任務添加到可執行棧中,開始執行。

A、JS引擎線程和事件觸發線程

在這裏插入圖片描述
上圖大致描述就是:

  • 主線程運行時會產生執行棧, 棧中的代碼調用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發條件後,如ajax請求完畢)
  • 而棧中的代碼執行完畢,就會讀取事件隊列中的事件,去執行那些回調
  • 如此循環
  • 注意,總是要等待棧中的代碼執行完畢後纔會去讀取事件隊列中的事件

B、單獨說說定時器

上述事件循環機制的核心是:JS引擎線程和事件觸發線程;下面說定時器線程

當使用setTimeout或setInterval時,它需要定時器線程計時,計時完成後就會將特定的事件推入事件隊列中。
W3C在HTML標準中規定,規定要求setTimeout中低於4ms的時間間隔算爲4ms。 (不過也有一說是不同瀏覽器有不同的最小時間設定)

setTimeout而不是setInterval

區別:

前者是,計時到後,就會推送去執行,執行一段時間後,纔開始下一次計時
後者是,間隔一段時間,不斷推送去執行,可能會導致累計效應(目前有優化,但還是有很多其他問題)

五、事件循環進階:macrotask與microtask

  • 進一步,JS中分爲兩種任務類型:macrotask宏任務和microtask微任務,在ECMAScript中,microtask稱爲jobs,macrotask可稱爲task
  • 瀏覽器爲了能夠使得JS內部task與DOM任務能夠有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行重新渲染 (task->渲染->task->…)
  • 補充:在node環境下,process.nextTick的優先級高於Promise,也就是可以簡單理解爲:在宏任務結束後會先執行微任務隊列中的nextTickQueue部分,然後纔會執行微任務中的Promise部分。

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190929114810349.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NpbmR5NjQ3,size_16,color_FFFFFF,t_70 =300x 500)

總結下運行機制:

  1. 執行一個宏任務(棧中沒有就從事件隊列中獲取)
  2. 執行過程中如果遇到微任務,就將它添加到微任務的任務隊列中
  3. 宏任務執行完畢後,立即執行當前微任務隊列中的所有微任務(依次執行)
  4. 當前宏任務執行完畢,開始檢查渲染,然後GUI線程接管渲染
  5. 渲染完畢後,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章