前端階段性總結(二):頁面渲染機制與性能優化

引言: 轉前端一年了,期間工做較忙,也沒時間整理一些知識體系,此係列文章是對前端基礎的一些回顧與總結。本文主要介紹瀏覽器工做的原理以及一些優化手段。

1、瀏覽器渲染過程

1. 瀏覽器的主要結構:

clipboard.png

2. 瀏覽器的多進程模型:

以chorme爲例:php

clipboard.png

  • Browser進程:瀏覽器的主進程,負責瀏覽器界面的顯示,各個頁面的管理,其餘各類進程的管理;
  • Renderer進程:頁面的渲染進程,負責頁面的渲染工做,Blink的工做主要在這個進程中完成(主要分紅render主線程和合成器線程);
  • NPAPI插件進程:每種類型的插件只會有一個進程,每一個插件進程能夠被多個Render進程共享;
  • GPU進程:最多隻有一個,當且僅當GPU硬件加速打開的時候纔會被建立,主要用於對3D加速調用的實現;
  • Pepper插件進程:同NPAPI插件進程,不一樣的是爲Pepper插件而建立的進程

須要注意的是,NPAPI是指瀏覽器對系統或外部的一些程序的調用接口,好比播放視頻的 flash 插件,而Pepper實際上是基於NPAPI改進的插件架構。css

3. 網頁請求過程:

簡單的url請求過程

4. 瀏覽器渲染過程

a. 主要流程:

主流的瀏覽器內核主要有2種,Webkit 和 Geoko ,雖然 chorme 如今的內核更換爲 blink ,但其實 blink是基於webkit的,差別不大。其渲染過程分別以下:html

  • webkit

clipboard.png

  • Geoko

clipboard.png

這兩個內核的渲染流程大同小異,主要的過程能夠總結爲下列5個:前端

  • DomTree: 解析html構建DOM樹。
  • CssomTree : 解析CSS生成CSSOM規則樹。
  • RenderObjectTree: 將DOM樹與CSSOM規則樹合併在一塊兒生成渲染對象樹。
  • Layout: 遍歷渲染樹開始佈局(layout),計算每一個節點的位置大小信息。
  • Painting: 將渲染樹每一個節點繪製到屏幕。

使用chorme瀏覽器的開發者工具,咱們很容易看到這5個過程的時間線,下面是segmentfault主頁的渲染截圖:vue

clipboard.png

能夠看到上述流程的耗時,甚至能夠統計到每一幀的耗時分佈,從而對影響渲染性能的代碼精肯定位。其中黃色爲JS,紫色爲Style和Layout,綠色爲Paint和Composite部分,選中每一個部分會顯示各自的花費時間等信息,能夠看出這個圖片中JS運行的時間太長。目前的顯示設備通常刷新率是60FPS,因此理想中每幀的時間最好爲16毫秒。html5

須要注意的一點是,這裏的步驟執行並無特定的順序,爲保證渲染的速度,瀏覽器一開始接收到html時就開始執行解析的過程,而且遇到須要重繪和重排的時候會重複執行這些步驟,下面咱們詳細介紹一下這5個過程。node

b. 具體流程

DOM樹的構建:

瀏覽器在接收到html文件後即開始解析和構建DOM樹,在碰到js代碼段時,因爲js代碼可能會改變dom的結構,因此爲避免重複操做,瀏覽器會中止dom樹構建,先加載並解析js代碼。而對於css,圖片,視頻等資源,則交由資源加載器去加載,這個過程是異步的,並不會阻礙dom樹的生成。這個過程須要注意的點是:react

  • display:none的元素、註釋存在於dom樹中
  • js會阻塞dom樹的構建從而阻塞其餘資源的併發加載,所以好的作法是將js放在最後加載
  • 對於可異步加載的js片斷加上 asyncdefer

CSSOM樹的構建:

瀏覽器在碰到<link><style> 標籤時,會解析css生成cssom , 固然,link標籤須要先將css文件加載完成才能解析。
須要注意的是:webpack

  • js 代碼會阻塞cssom的構建,在webkit內核中有所優化,只有js訪問css纔會阻塞
  • cssom的構建與dom樹的構建是並行的
  • 減小css的嵌套層級和合理的定義css選擇器能夠加快解析速度,可參考如何提高 CSS 選擇器性能

RenderObject樹的構建:

在cssom 和dom 樹都構建完成後,瀏覽器會將他們結合,生成渲染對象樹,渲染樹的每個節點,包含了可見的dom節點和節點的樣式 。
須要注意的是:git

  • renderObject樹 與 dom樹不是徹底對應的,不可見的元素如display:none 是不會放入渲染樹的。
  • visibility: hidden的元素在Render Tree中

佈局:

這一步是瀏覽器遍歷渲染對象樹,並根據設備屏幕的信息,計算出節點的佈局、位置,構建出渲染布局樹(render layout)。渲染布局樹輸出的就是咱們常說的盒子模型,須要注意的是:

  • floatabsolutefixed 的元素的位置會發生偏移
  • 咱們常說的脫離文檔流,其實就是脫離佈局樹

繪製:

瀏覽器對生成的佈局樹進行繪製,由用戶界面後端層將每一個節點繪製出來。此時,Webkit內核還須要將渲染結果從Renderer進程傳遞到Browser進程。

4. 重繪和迴流

前面講到,js代碼能夠訪問和修改dom節點和css,因此在解析js的過程當中會致使頁面從新佈局和渲染,這就是重繪(repaint)和迴流(reflow)。

a. 重繪:

概念:

重繪是指css樣式的改變,但元素的大小和尺寸不變,而致使節點的從新繪製。

重繪的觸發:

任何對元素樣式,如background-colorborder-colorvisibility 等屬性的改變。css 和 js 均可能引發重繪。

b. 迴流

概念

迴流(reflow)是指元素的大小、位置發生了改變,而致使了佈局的變化,從而致使了佈局樹的從新構建和渲染。

迴流的觸發

  • dom元素的位置和尺寸大小的變化
  • dom元素的增長和刪除
  • 僞類的激活
  • 窗口大小的變化
  • 增長和刪除class樣式
  • 動態計算修改css樣式

固然,咱們的瀏覽器不會每一次reflow都馬上執行,而是會積攢一批,這個過程也被成爲異步reflow,或者增量異步reflow。可是有些狀況瀏覽器是不會這麼作的,好比:resize窗口,改變了頁面默認的字體,等。對於這些操做,瀏覽器會立刻進行reflow。

2、頁面性能分析與測速

優化並非無目的的,而是經過分析頁面各個維度,找到亟待優化的方向或者具體到某段代碼。下面就討論一下如何對頁面作性能分析和測速監控。

1.性能分析

Chorme Devtools

chorme得devtools相信全部的前端開發者都用過,它不只提供了平常開發中極強的調試能力,同時也具有着極強的頁面分析能力。

第三方分析網站

2.測速上報

測速的關鍵指標

通常來講,咱們打開一個頁面,指望的是頁面的響應和呈現速度和流暢的交互體驗。因此,頁面的測速指標能夠大體歸納爲: 白屏時間,首屏時間,可交互時間。

clipboard.png

如何計算

window.performance是w3c提供的用來測量網頁和Web應用程序的性能api。其中performance timing提供了延時相關的性能信息,能夠高精度測量網站性能。timing的總體結構以下圖所示:

clipboard.png

  • 白屏時間=頁面開始展現的時間點(PerformanceTiming.domLoading)-開始請求時間點(PerformanceTiming.navigationStart)
  • 首屏時間=首屏內容渲染結束時間點(視業務具體狀況而定)-開始請求時間點(PerformanceTiming.navigationStart)
  • 可交互時間=用戶能夠正常進行事件輸入時間點(PerformanceTIming.domInteractive)-開始請求時間點(PerformanceTiming.navigationStart)

3、性能優化

關於性能優化,涉及的方向太廣了,從網絡請求到數據庫,整條鏈路都有其可優化的地方。這裏我只總結一下前端比較須要關注的一些優化點。這裏從兩個個維度進行討論:

(一). 網絡請求的優化

從上文可知,瀏覽器渲染網頁的前提是下載相關的資源,html文檔、css文檔、圖片資源等。這些資源是客戶端基於HTTP協議,經過網絡請求從服務器端請求下載的,你們都知道,有網絡,一定有延遲,而資源加載的網絡延遲,是頁面緩慢的一個重要因素。因此,如何使資源更快、更合理的加載,是性能優化的必修課。

1. 靜態資源

1)拼接、合併、壓縮、製做雪碧圖:

因爲HTTP的限制,在創建一個tcp請求時須要一些耗時,因此,咱們對資源進行合併、壓縮,其目的是減小http請求數和減少包體積,加快傳輸速度。

  • 拼接、合併、壓縮: 在現代的前端工程化開發流程中,相信你們都有使用webpack或者gulp等打包工具對資源(js、css、圖片等)進行打包、合併、去重、壓縮。在這基礎上,咱們須要根據自身的業務,合理的對公共代碼,公共庫,和首屏代碼進行單獨的打包壓縮,按需加載;
  • 雪碧圖:對於圖片資源,咱們能夠製做雪碧圖,即對一些頁面上的icon和小圖標,集成到一張圖片上,css使用背景圖定位來使用不一樣的icon,這樣作能夠有效的減小圖片的請求數,下降網絡延遲。而它的缺點也很明顯,因爲集成在同一張圖片上,使用其中的一個圖標,就須要將整張圖片下載下來,因此,雪碧圖不能盲目的使用。

clipboard.png

segmentfault.com 的雪碧圖圖標

2)CDN資源分發:

將一些靜態資源文件託管在第三方CDN服務中,一方面能夠減小服務器的壓力,另外一方面,CDN的優點在於,CDN系統可以實時地根據網絡流量和各節點的鏈接、負載情況以及到用戶的距離和響應時間等綜合信息將用戶的請求從新導向離用戶最近的服務節點上,保證資源的加載速度和穩定性。

3)緩存:

緩存的範圍很廣,好比協議層的DNS解析緩存、代理服務器緩存,到客戶端的瀏覽器本地緩存,再到服務端的緩存。一個網絡鏈路的每一個環節都有被緩存的空間。緩存的目的是簡化資源的請求路徑,好比某些靜態資源在客戶端已經緩存了,再次請求這個資源,只須要使用本地的緩存,而無需走網絡請求去服務端獲取。

clipboard.png

segmentfault 的主頁的一些靜態資源使用了緩存,上面是一些控制緩存的header首部字段

4)分片:

分片指得是將資源分佈到不一樣的主機,這是爲了突破瀏覽器對同一臺主機創建tcp鏈接的數量限制,通常爲6~8個。現代網站的資源數量有50~100個很常見,因此將資源分佈到不一樣的主機上,能夠創建更多的tcp請求,下降請求耗時,從而提高網頁速度。

clipboard.png

從segmentfault 的主頁請求能夠看出,網站將靜態js文件和圖片都放在了不一樣的子域名下。

5)升級協議:

能夠升級咱們的網絡協議,好比使用HTTP2,quic 之類的,代替以前的http1.1,從協議層優化資源的加載。能夠參考我以前的文章。

2. 業務數據

雖然作好了靜態數據的加載優化,可是仍是會出現一種情景,即靜態數據已經加載完畢,但頁面仍是在轉菊花,頁面尚未進入可交互狀態,這是由於現現在的網站開發模式,先後端分離已經成爲主流,再也不由php或jsp服務端渲染前端頁面,而是前端先加載靜態數據,再經過ajax異步獲取服務器的數據,進而從新渲染頁面。這就致使了異步從接口獲取數據也是網頁的一個性能瓶頸。響應緩慢,不穩定的接口,會致使用戶交互體驗極差,頁面渲染速度也不理想。好比點擊一個提交數據的按鈕,接口速度慢,頁面上菊花須要轉很久才能交換完數據。

1)首屏直出

爲了提高用戶體驗,咱們認爲首屏的渲染速度是極爲重要的,用戶進來頁面,首頁可見區域的加載能夠由服務端渲染,保證了首屏加載速度,而不可見的部分則能夠異步加載,甚至作到子路由頁面的預加載。業界已經有不少同構直出的方案,好比vue的nuxt , react的beidou等。

2)接口合併

前端常常有這樣的場景,完成一個功能須要先請求第一個接口得到數據,而後再根據數據請求第二個接口獲取第二個數據,而後第3、第四...前端一般須要經過promise或者回調,一層一層的then下去,這樣顯然是很消耗性能的
clipboard.png

一般後臺接口都按必定的粒度存在的,不可能一個接口知足全部的場景。這是不可避免的,那麼如何作到只發送一個請求就能實現功能呢?有一種不錯的方案是,代理服務器實現請求合併,即後臺的接口只須要保證健壯和分佈式,而由nodejs(固然也可使用其餘語言)建設一層代理中間層,流程以下圖所示:

clipboard.png

前端只須要按找約定的規則,向代理服務器發起一次請求,由代理服務器向接口服務器發起三次請求,再將目標數據返回給客戶端。這樣作的好處是:一方面是代理服務器代替前端作了接口合併,減小了前端的請求數量;另外一方面代理服務器能夠脫離HTTP的限制,使用更高效的通訊協議與服務器通訊;

(二). 頁面渲染性能的優化

1. 防止阻塞渲染

頁面中的css 和 js 會阻塞html的解析,由於他們會影響dom樹和render樹。爲了不阻塞,咱們能夠作這些優化:

  • css 放在首部,提早加載,這樣作的緣由是: 一般狀況下 CSS 被認爲是阻塞渲染的資源,在CSSOM 構建完成以前,頁面不會被渲染,放在頂部讓樣式表可以儘早開始加載。但若是把引入樣式表的 link 放在文檔底部,頁面雖然能馬上呈現出來,可是頁面加載出來的時候會是沒有樣式的,是混亂的。當後來樣式表加載進來後,頁面會當即進行重繪,這也就是一般所說的閃爍了。
  • js文件放在底部,防止阻塞解析
  • 一些不改變dom和css的js 使用 deferasync 屬性告訴瀏覽器能夠異步加載,不阻塞解析

2. 減小重繪和迴流

重繪和迴流在實際開發中是很難避免的,咱們能作的就是儘可能減小這種行爲的發生。

  • js儘可能少訪問dom節點和css 屬性
  • 儘量的爲產生動畫的 HTML 元素使用 fixedabsoluteposition ,那麼修改他們的 CSS 是不會 Reflow 的。
  • img標籤要設置高寬,以減小重繪重排
  • 把DOM離線後修改,如將一個dom脫離文檔流,好比display:none ,再修改屬性,這裏只發生一次迴流。
  • 儘可能用 transform 來作形變和位移,不會形成迴流

3. 提升代碼質量

這最能體現一個前端工程師的水平了,高性能的代碼能在實現功能的同時,還兼顧性能。下面是一些好的實踐:

1)html:

  • dom的層級儘可能不要太深,不然會增長dom樹構建的時間,js訪問深層的dom也會形成更大的負擔。
  • meta標籤裏須要定義文檔的編碼,便於瀏覽器解析

2)css:

3)js:

  • 減小經過JavaScript代碼修改元素樣式,儘可能使用修改class名方式操做樣式或動畫
  • 訪問dom節點時須要對dom節點轉存,防止循環中重複訪問dom節點形成性能損耗。
  • 慎用 定時器 和 計時器, 使用完後須要銷燬。
  • 用於複雜計算的js代碼能夠放在worker進程中運行
  • 對於一些高頻的回調須要對其節流和消抖,就是 debouncethrottle 這兩個函數。好比scrolltouch事件

...

優化沒有正確答案,優化的手段也層出不窮,這裏也沒法歸納全面,只列舉了一些我瞭解過的。其實除了前端,後端也有許多可優化的地方,好比接口緩存啊,數據庫緩存啊等等。這個本騷年就瞭解的不深了。

4、思考與總結

性能一直是前端開發很重要的一個課題。性能優化也是一條不見盡頭的路,任重而道遠啊~

參考文章:
https://segmentfault.com/a/11...
https://sylvanassun.github.io...
https://www.html5rocks.com/zh...
https://juejin.im/post/5a966b...
https://juejin.im/post/59672f...
https://tech.meituan.com/perf...
https://segmentfault.com/a/11...
https://www.jianshu.com/p/268...

相關文章
相關標籤/搜索