瀏覽器運行機制詳解

前言

 你們確定都據說過不少瀏覽器優化原則吧,例如說減小DOM操做,使用transformX(0)進行硬件優化,避免js文件執行時間過長使得頁面卡頓等等。大部分人可能都知道,但也僅限於知道,即知其然,不知其因此然。css

 學習要造成本身的知識體系,不然的話,每每是東一榔頭西一榔頭地學習知識,這樣致使學習到的知識鬆散,沒法造成內在的聯繫,也就致使了學習地不夠深刻,只是浮於表面,只是「記住」了知識。html

 因此,接下來,我想來爲你們梳理一下瀏覽器運行過程當中須要理解的知識,以下:前端

  • 前言
  • 進程與線程
  • 瀏覽器進程
    • 瀏覽器都有哪些進程
    • 瀏覽器內核(renderer進程)
    • html解析
    • css解析
    • render樹
    • 迴流與重繪
      • 何時會發生迴流與重繪
      • 具體什麼操做會引發迴流
      • 如何減小回流
    • 硬件加速
      • 如何才能使用硬件加速
      • 硬件加速使用z-index
    • 瀏覽器頁面的渲染流程
    • DOMContentLoaded和load事件
    • css堵塞狀況
    • js堵塞狀況
    • css和js文件應當放在html哪一個位置
    • 事件循環機制
    • 宏任務和微任務
    • 致使頁面沒法響應的緣由
    • html文件解析過程
  • 參考連接


進程與線程

 能夠這樣理解:

web


 - 進程是一個工廠,每一個工廠有其獨立的資源。

 - 線程是工廠中的工人,可能只有一個,可能有好多個。多個工人協同完成工做。工人共享工做資源。





 回到硬件上來理解:segmentfault


 - 工廠的資源 -> 系統分配的內存。

 - 工廠之間相互獨立 -> 進程之間相互獨立,也即進程分配到的內存相互獨立,沒法讀到對方內存中的數據。

 - 一個工廠有一個或多個工人 -> 一個線程中有一個或多個線程。

 - 多個工人協同完成工做 -> 進程中多個線程協同完成工做。即線程之間能互相發送請求與接收結果。

 - 工人共享工做資源 -> 進程中全部線程都能訪問到相同一塊內存,即信息是互通的。





 不過在這裏要強調一點:一個軟件不等於一個進程,一個軟件可能包含有多個互相獨立的進程。瀏覽器

 最後,再用官方的術語描述下進程與線程的差異網絡


  • 進程是系統資源分配的最小單位(即系統以進程爲最小單位分配內存空間,同時進程是能獨立運行的最小單位)


  • 線程是系統調度的最小單位(即系統以線程爲單位分配cpu中的核。)



 tips:多線程


  • 進程之間也能互相通訊,不過代價比較大。



瀏覽器進程

 首先,明確的是:瀏覽器是多線程的。併發

 以Chrome瀏覽器爲例:框架

 你們有興趣的話,也能夠打開Chrome的任務管理器測試。由圖可知,Chrome中有多個進程(每一個tab頁面對應一個進程,以及Browser進程,GPU進程和插件進程)。


瀏覽器都有哪些進程

瀏覽器中的進程分別是:


  • Browser進程 : 是瀏覽器的主進程,負責主控,協調,只有一個,能夠看作是瀏覽器的大腦。
    • 負責下載頁面的網絡文件
    • 負責將renderer進程獲得的存在內存中的位圖渲染(顯示)到頁面上
    • 負責建立和銷燬tab進程(renderer進程)
    • 負責與用戶的交互
  • GPU進程 : 只有一個。
    • 負責3D繪製,只有當該頁面使用了硬件加速纔會使用它,來渲染(顯示)頁面。不然的話,不使用這個進程,而是用Browser進程來渲染(顯示)頁面
  • renderer進程:又名瀏覽器內核,每一個tab頁面對應一個獨立的renderer進程,內部有多個線程。
    • 負責腳本執行,位圖繪製,事件觸發,任務隊列輪詢等
  • 第三方插件進程:每種類型的插件對應一個進程。



 瀏覽器是多進程的好處很是明顯,若是瀏覽器是單線程的話,則一個頁面,一個插件的崩潰會致使整個瀏覽器崩潰,用戶體驗感會很是差。


瀏覽器內核(renderer進程)

 ,弄懂了這一部分的知識,那麼你對一個網頁的運行機制也就能有個框架了。

 renderer進程是多線程的,如下是各個線程的名稱及做用(僅列舉常駐線程):


  • js引擎線程:
    • 也稱js內核,解析js腳本,執行代碼
    • 與GUI線程互斥,即當js引擎線程運行時,GUI線程會被掛起,當js引擎線程結束運行時,纔會繼續運行GUI線程
    • 由一個主線程和多個web worker線程組成,因爲web worker是附屬於主線程,沒法操做dom等,因此js仍是單線程語言(在主線程運行js代碼)
  • GUI渲染線程:
    • 用於解析html爲DOM樹,解析css爲CSSOM樹,佈局layout,繪製paint
    • 當頁面須要重排reflow,重繪repaint時,使用該線程
    • 與js引擎線程互斥
  • 事件觸發線程
    • 當對應事件觸發(不管是WebAPIs完成事件觸發,仍是頁面交互事件觸發)時,該線程會將事件對應的回調函數放入callback queue(任務隊列)中,等待js引擎線程的處理
  • 定時觸發線程
    • 對應於setTimeout,setInterval API,由該線程來計時,當計時結束,將事件對應的回調函數放入任務隊列中
    • 當setTimeout的定時的時間小於4ms,一概按4ms來算
  • http請求線程
    • 每有一個http請求就開一個該線程
    • 當檢測到狀態變動的話,就會產生一個狀態變動事件,若是該狀態變動事件對應有回調函數的話,則放入任務隊列中
  • 任務隊列輪詢線程
    • 用於輪詢監放任務隊列,以知道任務隊列是否爲空



 想必你們對renderer進程裏的組成及職能有個大概的認知了,接下來,咱們會着重於細節來進行研究。


html解析

 html解析包含有一系列的步驟,過程爲Bytes -> Characters -> Tokens -> Nodes -> DOM。最終將html解析爲DOM樹。

 假設有一html頁面,代碼以下:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

 處理過程以下:

 最終生成的DOM樹:


css解析

 與html解析相似,他解析最終造成CSSOM樹,過程爲Bytes -> Characters -> Tokens -> Nodes -> CSSOM。

 假設css代碼以下:

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }

 獲得的CSSOM爲:

render樹

 由DOM樹與CSS樹結合造成的渲染樹(其中沒法顯示的元素,如script,head元素或diplay:none的元素,不會在渲染樹中,也就最終不會被渲染出來),頁面的佈局,繪製都是以render樹爲依據。

 由以上的DOM樹與CSSOM樹,最終獲得的渲染樹以下:


迴流與重繪

 在此以前,咱們先明確另外兩個概念:佈局與繪製。






 - 佈局是頁面首次加載時進行的操做,從新佈局即爲迴流。




 - 繪製是頁面首次加載時進行的操做,從新繪製即爲重繪。



何時會發生迴流和重繪呢:


  • 當頁面的某部分元素髮生了尺寸、位置、隱藏發生了改變,頁面進行迴流。得對整個頁面從新進行佈局計算,將全部尺寸,位置受到影響的元素迴流
  • 當頁面的某部分元素的外觀發生了改變,但尺寸、位置、隱藏沒有改變,頁面進行重繪。(一樣,只重繪部分元素,而不是整個頁面重繪)



迴流的同時每每會伴隨着重繪,重繪不必定致使迴流。因此迴流致使的代價是大於重繪的。

 若是你們對這二者的差異還不是很清楚的話,我引用這兩張圖給你們:

迴流

重繪

那麼具體什麼操做會引發迴流呢:


  • 頁面初始化渲染
  • 窗口的尺寸變化
  • 元素的尺寸、位置、隱藏變化
  • DOM結構發生變化,如刪除節點
  • 獲取某些屬性,引起迴流
    • 不少瀏覽器會對迴流進行優化,必定時間段後或數量達到闋值時,作一次批處理迴流。
    • 當獲取一些屬性時,瀏覽器爲了返回正確的值也會觸發迴流,致使瀏覽器優化無效,有:
      1. offset(top/bottom/left/right)
      2. client (top/bottom/left/right)
      3. scroll (top/bottom/left/right)
      4. getComputedStyle()
      5. width,height
    • 其次,字體大小修改及內容更新也會致使迴流



 頻繁的迴流與重繪會致使頻繁的頁面渲染,致使cpu或gpu過量使用,使得頁面卡頓。

那麼如何減小回流呢:


  1. 減小逐項更改樣式,最好一次性更改style,或是將更改的樣式定義在class中並一次性更新
  2. 避免循環操做DOM,而是新建一個節點,在他上面應用全部DOM操做,而後再將他接入到DOM中
  3. 當要頻繁獲得如offset屬性時,只讀取一次而後賦值給變量,而不是每次都獲取一次
  4. 將複雜的元素絕對定位或固定定位,使他脫離文檔流,不然迴流代價很高
  5. 使用硬件加速建立一個新的複合圖層,當其須要迴流時不會影響原始複合圖層迴流


硬件加速

 咱們在未開啓硬件加速的時候是使用cpu來渲染頁面,只有開啓了硬件加速了,纔會使用到GPU渲染頁面。

 在詳細講解硬件加速前,咱們先來說解一下簡單圖層和複合圖層


  • DOM中的每一個結點對應一個簡單圖層
  • 複合圖層是各個簡單圖層的合併,一個頁面通常來講只有一個複合圖層,不管你建立了多少個元素,都是在這個複合圖層中
    • 其次,absolute、fixed佈局,可使該元素脫離文檔流,但仍是在這個複合圖層中,因此他仍是會影響複合圖層的繪製,但不會影響重排



當一個元素使用硬件加速後,會生成一個新的複合圖層,這樣無論其如何變化,都不會影響原複合圖層。不過不要大量使用硬件加速,會致使資源消耗過分,致使頁面也卡。

 因此,使用了硬件加速後,會有多個複合圖層,而後多個複合圖層互相獨立,單獨佈局、繪製。

如何才能使用硬件加速;


  1. translate3d,translateZ

  2. opacity屬性


硬件加速時請使用z-index

 具體原理是這樣的:

 當一個元素使用了硬件加速,在其後的元素,若z-index比他大或者相同,且absolute或fixed的屬性相同,則默認爲這些元素也建立各自的複合圖層。

 因此咱們人爲地爲這個元素添加z-index值,從而避免這種狀況


瀏覽器頁面的渲染流程

 通過以上的學習,咱們能夠清楚瀏覽器的渲染過程了:


 1. 解析html獲得DOM樹

 2. 解析css獲得CSS樹

 3. 合併獲得render樹

 4. 佈局,當頁面有元素的尺寸、大小、隱藏有變化或增長、刪除元素時,從新佈局計算,並修改頁面中全部受影響的部分

 5. 繪製,當頁面有元素的外觀發生變化時,從新繪製

 6. GUI線程將獲得的各層的位圖(每一個元素對應一個普通圖層)發送給Browser進程,由Browser進程將各層合併,渲染在頁面上


DOMContentLoaded和load事件

 這二者的差異,由其定義就可知:


 - DOMContentLoaded:當DOM加載完成觸發

 - load:當DOM,樣式表,腳本都加載完時觸發



 因此能夠知道,DOMContentLoaded在load以前觸發


css的堵塞狀況

 首先,是在Browser進程中下載css文件,當下載完成後,發送給GUI線程。

 其次,是在GUI線程中解析html及css,不過這二者是並行的。

 因爲css的下載和解析不會影響DOM樹,因此不會堵塞html文件的解析,但會堵塞頁面渲染。

 這樣的設計是很是合理的,若是css文件的下載和解析不會堵塞頁面渲染,那麼在頁面渲染的途中或結束後發現元素樣式有變化,則又須要迴流和重繪。


js的堵塞狀況

 明確的是,js文件的下載和解析執行都會堵塞html文件的解析及頁面渲染。

 由於js腳本可能會改變DOM結構,如果其不堵塞html文件的解析及頁面渲染的話,那麼當js腳本改變DOM結構或元素樣式時,會引起迴流和重繪,會形成沒必要要的性能浪費,不如等待js執行完,在進行html解析和頁面渲染。

 若是你不想js堵塞的話,則使用async屬性,這樣就能夠異步加載js文件,加載完成後當即執行。


css和js文件應當放在html哪一個位置

js:



 當須要在DOM樹完成以前用js進行初始化操做的話,在head中使用js。

 若是是須要在DOM樹造成以後,即要操做DOM,則在body元素的末尾。不過也可使用load事件。

 若是js的內容比較小,則推薦使用內部js而不是引用js,這樣能夠減小http請求。



css:


 通常放在head中,由於css的解析不影響html的解析,因此越早引入,越早同時解析。


事件循環機制

 事件循環機制在個人這篇文章有詳細的說明:http://www.javashuo.com/article/p-wjqgadlf-n.html

 總結一句話:

事件循環機制的核心是事件觸發線程,因爲執行棧產生異步任務,異步任務完成後事件觸發線程將其回調函數傳入到任務隊列中,當執行棧爲空,任務隊列將隊列頭的回調函數入執行棧,從而新的一輪循環開始。這就是稱爲循環的緣由。


宏任務和微任務

宏任務(macrotask):


 - 主代碼塊和任務隊列中的回調函數就是宏任務。

 - 爲了使js內部宏任務和DOM任務可以有序的執行,每次執行完宏任務後,會在下一個宏任務執行以前,對頁面從新進行渲染。(宏任務 -> 渲染 -> 宏任務)


微任務(microtask):


 - 在宏任務執行過程當中,執行到微任務時,將微任務放入微任務隊列中。

 - 在宏任務執行完後,在從新渲染以前執行。

 - 當一個宏任務執行完後,他會將產生的全部微任務執行完。


分別在什麼場景下會產生宏任務或微任務呢:

  • 宏任務:主代碼塊,setTimeout,setInterval(任務隊列中的全部回調函數都是宏任務)

  • 微任務:Promise


致使頁面沒法當即響應的緣由

 致使頁面沒法響應的緣由是執行棧中還有任務未執行完,或者是js引擎線程被GUI線程堵塞。


html文件解析過程

 這個過程是在下載html文件以後,不包括網絡請求過程


 1. Browser進程下載html文件並將文件發送給renderer進程

 2. renderer進程的GUI進程開始解析html文件來構建出DOM

 3. 當遇到外源css時,Browser進程下載該css文件併發送回來,GUI線程再解析該文件,在這同時,html的解析也同時進行,但不會渲染(還未造成渲染樹)

 4. 當遇到內部css時,html的解析和css的解析同時進行

 5. 繼續解析html文件,當遇到外源js時,Browser進程下載該js文件併發送回來,此時,js引擎線程解析並執行js,由於GUI線程和js引擎線程互斥,因此GUI線程被掛起,中止繼續解析html。直到js引擎線程空閒,GUI線程繼續解析html。

 6. 遇到內部js也是同理

 7. 解析完html文件,造成了完整的DOM樹,也解析完了css,造成了完整的CSSOM樹,二者結合造成了render樹

 8. 根據render樹來進行佈局,若在佈局的過程當中發生了元素尺寸、位置、隱藏的變化或增長、刪除元素時,則進行迴流,修改

 9. 根據render樹進行繪製,若在佈局的過程當中元素的外觀發生變換,則進行重繪

 10. 將佈局、繪製獲得的各個簡單圖層的位圖發送給Browser進程,由它來合併簡單圖層爲複合圖層,從而顯示到頁面上

 11. 以上步驟就是html文件解析全過程,完成以後,如若當頁面有元素的尺寸、大小、隱藏有變化時,從新佈局計算迴流,並修改頁面中全部受影響的部分,如若當頁面有元素的外觀發生變化時,重繪



 (完)


參考連接

1.CSS3硬件加速也有坑http://web.jobbole.com/83575/

2.瀏覽器渲染過程、迴流、重繪簡介https://blog.csdn.net/cxl444905143/article/details/42005333

3.頁面優化,談談重繪(repaint)和迴流(reflow)http://www.javashuo.com/article/p-zlayxuan-bo.html

4.你真的瞭解迴流和重繪嗎http://www.javashuo.com/article/p-wnorqsqz-cb.html

5.css加載會形成阻塞嗎?http://www.javashuo.com/article/p-dcjijisn-da.html

6.從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理http://www.javashuo.com/article/p-apnenkgt-db.html

7.從輸入URL到頁面加載的過程?如何由一道題完善本身的前端知識體系!http://www.javashuo.com/article/p-ruyhecrz-do.html

相關文章
相關標籤/搜索