前言
你們確定都據說過不少瀏覽器優化原則吧,例如說減小DOM操做,使用transformX(0)進行硬件優化,避免js文件執行時間過長使得頁面卡頓等等。大部分人可能都知道,但也僅限於知道,即知其然,不知其因此然。css
學習要造成本身的知識體系,不然的話,每每是東一榔頭西一榔頭地學習知識,這樣致使學習到的知識鬆散,沒法造成內在的聯繫,也就致使了學習地不夠深刻,只是浮於表面,只是「記住」了知識。html
因此,接下來,我想來爲你們梳理一下瀏覽器運行過程當中須要理解的知識,以下:前端
- 前言
- 進程與線程
- 瀏覽器進程
- 瀏覽器都有哪些進程
- 瀏覽器內核(renderer進程)
- html解析
- css解析
- render樹
- 迴流與重繪
- 何時會發生迴流與重繪
- 具體什麼操做會引發迴流
- 如何減小回流
- 硬件加速
- 瀏覽器頁面的渲染流程
- 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結構發生變化,如刪除節點
- 獲取某些屬性,引起迴流
- 不少瀏覽器會對迴流進行優化,必定時間段後或數量達到闋值時,作一次批處理迴流。
- 當獲取一些屬性時,瀏覽器爲了返回正確的值也會觸發迴流,致使瀏覽器優化無效,有:
- offset(top/bottom/left/right)
- client (top/bottom/left/right)
- scroll (top/bottom/left/right)
- getComputedStyle()
- width,height
- 其次,字體大小修改及內容更新也會致使迴流
頻繁的迴流與重繪會致使頻繁的頁面渲染,致使cpu或gpu過量使用,使得頁面卡頓。
那麼如何減小回流呢:
- 減小逐項更改樣式,最好一次性更改style,或是將更改的樣式定義在class中並一次性更新
- 避免循環操做DOM,而是新建一個節點,在他上面應用全部DOM操做,而後再將他接入到DOM中
- 當要頻繁獲得如offset屬性時,只讀取一次而後賦值給變量,而不是每次都獲取一次
- 將複雜的元素絕對定位或固定定位,使他脫離文檔流,不然迴流代價很高
- 使用硬件加速建立一個新的複合圖層,當其須要迴流時不會影響原始複合圖層迴流
硬件加速
咱們在未開啓硬件加速的時候是使用cpu來渲染頁面,只有開啓了硬件加速了,纔會使用到GPU渲染頁面。
在詳細講解硬件加速前,咱們先來說解一下簡單圖層和複合圖層
- DOM中的每一個結點對應一個簡單圖層
- 複合圖層是各個簡單圖層的合併,一個頁面通常來講只有一個複合圖層,不管你建立了多少個元素,都是在這個複合圖層中
- 其次,absolute、fixed佈局,可使該元素脫離文檔流,但仍是在這個複合圖層中,因此他仍是會影響複合圖層的繪製,但不會影響重排
當一個元素使用硬件加速後,會生成一個新的複合圖層,這樣無論其如何變化,都不會影響原複合圖層。不過不要大量使用硬件加速,會致使資源消耗過分,致使頁面也卡。
因此,使用了硬件加速後,會有多個複合圖層,而後多個複合圖層互相獨立,單獨佈局、繪製。
如何才能使用硬件加速;
- translate3d,translateZ
- 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