本文原創(chuàng):gaoruixia
一. 開篇
提起前端性能優(yōu)化歼郭,大家會想到哪些內(nèi)容呢雇寇?
相信不同的人給出的答案不同痰哨,關(guān)于性能優(yōu)化沒有標準答案诱渤,它更像是一個摸索的過程丐巫,本篇文章給出從頁面加載的各個過程分析可優(yōu)化的點
二. 常用性能優(yōu)化指標
下面這張圖是一個頁面從打開到加載完成經(jīng)歷的各個階段
-
瀏覽器獲得各個階段的時間耗時的API
- Timing標準:window.performance.timing - Timing2標準: window.performance.getEntriesByType('navigation')[0]。
Timing目前瀏覽器已不再提供維護支持勺美,Timing2獲取的精度更加準確(比飛秒還精確鞋吉,為10的-17次方秒),但兼容性尚且沒有那么好(主要是iOS11才開始支持励烦,其他瀏覽器都基本沒問題了谓着,IE9我們就讓它見鬼去吧)。在京麥插件性能統(tǒng)計中坛掠,我們優(yōu)先判斷是否支持后者赊锚,除非后者不支持才使用前者。
-
白屏時間
從navigatorStart到responseEnd這段時間算作服務(wù)器時間
從瀏覽器開始加載頁面到首次出現(xiàn)內(nèi)容之前的這段時間(從頁面發(fā)送一個頁面URL請求出去屉栓,到服務(wù)器返回這個HTML的文本內(nèi)容)計算方式 - Timing: performance.timing.responseEnd - performance.timing.navigationStart - Timing2: performance.getEntriesByType('navigation')[0].responseStart
-
瀏覽器渲染時間
從responseEnd到loadEventEnd這段時間舷蒲,包括CSS、JavaScript友多、Image等資源的加載耗時
計算方式 - Timing: performance.timing.loadEventEnd - performance.timing.responseEnd - Timing2: performance.getEntriesByType('navigation')[0].loadEventEnd - performance.getEntriesByType('navigation')[0].responseStart
-
整頁時間
從瀏覽器開始加載頁面到整個頁面加載完畢(最明顯的標識就是移動端瀏覽器進度條讀完了牲平,pc端瀏覽器就是當前頁簽前面的loading消失了)
計算方式 - Timing: performance.timing.loadEventEnd - performance.timing.navigationStart - Timing2: performance.getEntriesByType('navigation')[0].loadEventEnd
使用Chrome調(diào)試工具performance查看瀏覽器請求一個頁面到渲染完成的過程
可以打開瀏覽器控制臺,切換到Performance域滥,點擊刷新按鈕纵柿,等頁面加載完成
三. 影響整頁時間的因素
先了解下得到html文本內(nèi)容后頁面的加載渲染流程(以webkit主流程為例)
-
渲染流程有四個主要步驟
- 解析HTML生成DOM樹 - 渲染引擎首先解析HTML文檔蜈抓,生成DOM樹
- 構(gòu)建Render樹 - 接下來不管是內(nèi)聯(lián)式,外聯(lián)式還是嵌入式引入的CSS樣式會被解析生成CSSOM樹昂儒,根據(jù)DOM樹與CSSOM樹生成另外一棵用于渲染的樹-渲染樹(Render tree)沟使,
- 布局Render樹 - 然后對渲染樹的每個節(jié)點進行布局處理,確定其在屏幕上的顯示位置
- 繪制Render樹 - 最后遍歷渲染樹并用UI后端層將每一個節(jié)點繪制出來
以上步驟是一個漸進的過程渊跋,為了提高用戶體驗腊嗡,渲染引擎試圖盡可能快的把結(jié)果顯示給最終用戶疙剑。它不會等到所有HTML都被解析完才創(chuàng)建并布局渲染樹澳泵。它會在從網(wǎng)絡(luò)層獲取文檔內(nèi)容的同時把已經(jīng)接收到的局部內(nèi)容先展示出來束世。
-
阻塞渲染的因素
沒有js的理想情況下胸私,html與css會并行解析导帝,分別生成DOM與CSSOM囊颅,然后合并成Render Tree湾戳,進入Rendering Pipeline弟劲;
但如果有js蟋软,css加載會阻塞后面js語句的執(zhí)行镶摘,而(同步)js腳本執(zhí)行會阻塞其后的DOM解析(所以通常會把css放在頭部,js放在body尾)-
CSS的阻塞情況
- css不會阻塞DOM樹的解析
- css會阻塞DOM樹的渲染
- css會阻塞后面js語句的執(zhí)行
-
JS的阻塞情況
- JS阻塞DOM解析岳守,但瀏覽器會"偷看"DOM凄敢,預(yù)先下載相關(guān)資源
- 瀏覽器遇到script且沒有defer或async屬性的標簽時,會觸發(fā)頁面渲染湿痢,因而如果前面CSS資源尚未加載完畢時涝缝,瀏覽器會等待它加載完畢在執(zhí)行腳本
-
藍色線代表網(wǎng)絡(luò)讀取,紅色線代表執(zhí)行時間譬重,這倆都是針對腳本的拒逮;綠色線代表HTML解析
-
由上可以得出影響整頁時間的因素
- CSS、JS臀规、圖片等資源加載
- CSS滩援、JS文件放置位置,避免阻塞DOM解析和渲染
- ajax同步請求
- jsonp請求
- 頁面加載過程中JS發(fā)送圖片請求
四. 優(yōu)化分析
使用Chrome調(diào)試工具performance
下圖中塔嬉,在NetWork資源加載瀑布圖中我們可以查看是主要是哪些資源卡住了頁面的整頁時間玩徊,發(fā)送結(jié)束時間都可以觀察到。之后再根據(jù)哪個請求是瓶頸谨究,對那個請求進行分析
使用Chrome插件pagespeed和Lighthouse
-
使用插件pagespeed
首先去安裝恩袱,安裝完pagespeed之后,打開你要調(diào)試的網(wǎng)頁胶哲,打開控制臺的pagespeed畔塔,然后點擊左上角的ANALYZE按鈕,開始分析頁面性能情況
插件Lighthouse類似,這里不詳細介紹了
對于整頁時間較長的情況澈吨,需要分析具體是在哪個階段導(dǎo)致整頁時間比較長把敢,可以從上面整頁時間的說明里的各個階段去分析
整頁時間主要在dom解析耗時上,這段時間是從HTML開始解析棚辽,到JS全部執(zhí)行完畢,這之中包含了CSS冰肴、JS等資源的加載屈藐,需要去優(yōu)化HTML解析這段時間。
五. 各階段優(yōu)化手段
從常用性能優(yōu)化指標我們知道了頁面加載總共分成下面這幾個階段:
重定向時間 -> DNS緩存查詢時間 -> DNS查詢時間 -> TCP連接時間 -> Request請求時間 -> Response請求響應(yīng)時間 -> DOM解析時間 -> DOM渲染時間 -> Load事件執(zhí)行時間
下面來看下各階段的優(yōu)化手段
1. 重定向時間
這個很簡單熙尉,就是請求到正確的地址上即可联逻。
經(jīng)常會出現(xiàn)的情況是:
- 將index.html的文件地址請求寫成這樣:
https://xx.com/xx
,這樣會導(dǎo)致瀏覽器重定向到https://xx.com/xx/
- 將http請求重定向到https上
這種情況注意下即可
2. DNS緩存查詢時間
這個是瀏覽器做的處理检痰,如Chrome瀏覽器對DNS的緩存時間是1分鐘包归。
這個階段我們無法優(yōu)化。
3. DNS查詢時間
HTML的請求的DNS查詢時間我們無法縮短铅歼,但對于里面的其他域名的請求公壤,我們可以提前進行DNS查詢,減少資源或者接口的請求時間椎椰。
在HTMl的頭部head中加上DNS預(yù)查詢即可
<link rel="dns-prefetch" >
一般對CDN需要用到的域名做解析即可厦幅。以下是CDN的各個域名:
4. TCP連接時間
TCP連接時間主要在3次握手中,縮短這個時間就是拉近用戶和服務(wù)器機房的距離慨飘,對于接口的請求這個很難做到确憨,但對于靜態(tài)資源的TCP連接時間,我們可以通過CDN全國節(jié)點縮短用戶與服務(wù)器之間的距離瓤的。
另外休弃,我們還可以用HTTP2的keep-alive來保持長連接,這個CDN服務(wù)器默認是開啟的圈膏,對于接口服務(wù)器也可以進行開啟塔猾,雖然異步Ajax請求并不影響整頁時間,但能讓用戶早點看到內(nèi)容稽坤,何樂而不為桥帆。
5. Request請求時間 和 Response請求響應(yīng)時間
在Request請求中,請求返回的時間取決于服務(wù)器的處理時間慎皱。
對于前后端耦合的項目或者SSR的項目老虫,因為需要處理邏輯,拿出數(shù)據(jù)再動態(tài)構(gòu)建HTML文本茫多,之后再將HTML文本返回給瀏覽器祈匙,那這段時間主要在于處理邏輯上。
對于前后端分離的項目,因為返回的是一個空的HTML文本夺欲,數(shù)據(jù)都是等JS調(diào)用Ajax獲取的跪帝,所以HTML的Request請求時間很短。但這樣我們得處理Ajax請求些阅,它也有Request請求的時間伞剑,這個也得看怎么在服務(wù)端進行性能提升。
雖然Ajax請求不計算在整頁時間中市埋,但也別為了縮短整頁時間而選擇前后端分離黎泣,這個并不是它的優(yōu)勢所在。用戶關(guān)心的是頁面什么時候加載完缤谎,這其中包含數(shù)據(jù)什么時候展現(xiàn)抒倚,所以別為了整頁時間的優(yōu)化而優(yōu)化,而應(yīng)該關(guān)心用戶體驗坷澡,就算是Ajax接口也應(yīng)該考慮怎么提升性能托呕。
而且使用前后端分離其實會加長頁面的白屏時間,這個也是一個衡量頁面性能的一個重要指標频敛,白屏時間可以通過添加骨架屏來優(yōu)化项郊。
對于靜態(tài)資源的請求,一般資源越小斟赚,請求越快(也受服務(wù)器帶寬的影響)呆抑。可以通過減小靜態(tài)資源(JS汁展、CSS鹊碍、圖片等)請求的大小從而來縮短請求響應(yīng)時間。
6. DOM解析時間
這段時間從服務(wù)器響應(yīng)返回HTML文本食绿,瀏覽器開始按照從上到下的執(zhí)行順序解析HTML文本侈咕,到執(zhí)行完HTML中的所有JS,這段時間我們將其稱為DOM解析時間器紧。所以優(yōu)化這段時間在于縮短HTML中的CSS文件和JS文件的請求時間和執(zhí)行時間耀销,以及縮短HTML的解析時間。
縮短CSS文件和JS文件的請求時間
可以從以下幾方面來做
- 靜態(tài)資源放在CDN上
- 壓縮文件(代碼混淆壓縮铲汪,Gzip)
- js文件拆分按需加載(webpack合理配置, 可使用webpack-bundle-analyzer來分析打包)
縮短HTML的解析時間
- 從前面影響整頁時間的因素中我們可以知道熊尉,CSS會阻塞HTMl的渲染和JS的執(zhí)行,JS會阻塞HTML的解析和渲染掌腰,因此為了避免阻塞狰住,要嚴格遵守CSS文件放在head頭部,JS文件放在body尾部
- 減少DOM樹的嵌套深度齿梁,這個屬于代碼層次的細節(jié)優(yōu)化催植,一般在于代碼書寫習慣上肮蛹,這個不好優(yōu)化
- 優(yōu)化JS代碼執(zhí)行邏輯,避免耗時處理创南,必要時可以使用Web Worker開啟多線程
7. DOM渲染時間
這個時間就在于減少重排(頁面布局變動)和重繪(頁面重新渲染元素樣式)
減少重排和重繪的手段
- JS 中 CSS 屬性讀寫分離
- 切換 class 或者 style.csstext 屬性來批量修改樣式
- 將沒用的元素設(shè)為不可見
- 壓縮 Dom 的深度伦忠,多使用偽元素或者 box-shadow
- 指定 img 標簽的大小
- 使用獨立渲染層(觸發(fā)新的渲染層的方式或元素:Video元素,WebGL稿辙,Canvas昆码,CSS3 3D,CSS濾鏡邻储,z-index大于相鄰節(jié)點)
- DOM 元素離線更新(使用appendChild和Document Fragment)
8. Load事件執(zhí)行時間
這個時間的壓縮就是減少onload回調(diào)函數(shù)的耗時處理
很多人習慣將事件綁定放在onload事件中赋咽,其實是可以將其提前,放在尾部JS中直接執(zhí)行或者DOMContentLoaded事件中芥备,這時候DOM解析完成了冬耿,已經(jīng)可以獲取到DOM節(jié)點了
總結(jié)
本文從頁面加載的各個階段分析影響因素舌菜,簡單的給出了可優(yōu)化的方向萌壳,具體實施還需要在工作中結(jié)合團隊現(xiàn)狀來進行