前端網頁加載渲染鏈路優化

優化實戰

本文屬於思否課堂VirtualDOM到AST玩轉前端性能原理解析與代碼實戰課程
官方博客:fed123.comjavascript

咱們已經全面分析總結了評估頁面性能和用戶體驗的各個指標參數。那麼怎麼來優化呢?open signal官方提供了2018年2月份統計的全世界4G網絡覆蓋率和通訊速率的統計分佈圖以下,在目前移動互聯網的浪潮下,咱們要利用好用戶終端設備的每一個字節的流量。
img_5bfa838fc0b92.png
固然頁面性能和體驗優化並非一蹴而就的,須要不斷的研究、跟蹤,發現問題,解決問題。可是咱們能夠在一開始編寫業務代碼的時候就作的更好,作到極致。因此,關於優化實戰咱們主要分爲兩部分:加載渲染鏈路優化 和 編程代碼優化。css

加載渲染鏈路優化

從訪問url到頁面呈現,整個鏈路能夠作優化的思路。
img_5bfa7f718a373.pnghtml

幸運的是,W3C推薦的Navigation Timing標準中所定義的核心的頁面性能數據,它包含了從上個頁面銷燬到跳轉到當前頁面加載完成每一個階段所消耗的時間。在canIuse上查到的兼容性也很好:
img_5bf95fcf80c58.png
利用這個接口能夠很方便的幫助咱們排查鏈路問題。在Navigation Timing標準中介紹到這個API主要包含兩個接口:PerformanceTiming和PerformanceNavigation,這兩個接口由瀏覽器進行實現和維護,當瀏覽器建立頁面的時候就會把接口定義的相關數據掛載到window.performance.timing和window.performance.navigation這兩個屬性上。咱們能夠打開一個網頁看一下:
img_5bf959bc391d4.png
img_5bf9615149eb5.png
咱們把這兩個圖對比一下,就能夠很容易的排查出頁面的加載鏈路問題。前端

靜態資源鏈路

打開頁面的第一步是請求頁面的html,這裏面涉及TTFB這個綜合指標。同時若是有必要咱們也能夠統計DNS時間和TCP時間。html5

DNS時間:主要是根據請求域名查詢到對應主機IP的時間。這個和DNS服務器有關係,也可能和本地緩存有關,若是這個很慢,能夠找服務商排查下問題。java

TCP時間:tcp是承接http協議的下層協議。主要是路由到主機ip,並創建tcp連接的時間。這個時間反應了服務器到用戶客戶端之間鏈路是否通暢,網絡是否通暢。node

請求完HTML以後,就開始解析html代碼,按照從上至下、天然順序解析,解析內聯CSS代碼或者加載外鏈CSS腳本,解析內聯Javascript腳本,或者加載外鏈Javascript腳本。因爲瀏覽器是單線程的,這些CSS和Javascript腳本極可能就會形成頁面卡頓。參考 瀏覽器線程理解與microtask與macrotaskreact

加載

CDN是內容分發網絡,主要用於緩存靜態資源。CDN服務商通常會在全國各地部署服務,並且帶寬很大,這樣訪問CDN的資源時就能夠有較短的路由路徑,並且帶寬也比較大,訪問比較快。android

  1. 建議最好把html, CSS、JS、font、img這些資源放在CDN上,沒有CDN也能夠放在OSS存儲服務上,總之比本身的服務器硬盤快多了,至少服務商會在不一樣區域作分佈式部署
  2. 若是沒有錢買CDN服務,那麼就儘量少的加載外聯CSS和JS代碼,注意html頭部能夠增長dns-prefetch,減小DNS解析時間
  3. 不是在首屏展現的資源,不要當即加載,能夠在頁面onload以後加載,或者首屏渲染完成再加載
  4. 壓縮CSS、JS、font、img,儘可能減小體積,服務端開啓gzip
  5. 考慮資源combo請求,減小http請求量,瀏覽器通常都有併發限制, 好比chrome一次6個併發http請求,不一樣瀏覽器內核可能不同。
  6. <script>加載腳本會阻塞瀏覽器主線程,考慮異步化,參考 script標籤的defer與async
  7. 利用好緩存,利用好http響應頭緩存字段,開啓靜態資源緩存,減小資源下載,建議開啓service worker緩存,這個是做爲APP Cache的替代方案,參考MDN;
  8. 對於單純的獲取數據作展現,儘可能採用JSONP請求數據,而不是AJAX,提高數據請求性能。參考jsonp而不是AJAX
  9. 開啓HTTP/2 ,HTTP2支持連接複用,能夠很高效下載多個小文件。HTTP/2 的目的是經過支持完整的請求與響應複用來減小延遲,經過有效壓縮 HTTP 標頭字段將協議開銷降至最低,同時增長對請求優先級和服務器推送的支持。

解析渲染

加載完JS和CSS以後,瀏覽器開始解析執行。Chrome的渲染流程是這樣的:(能夠參考 高性能CSS動畫
img_5bfd32b751afb.png
爲了讓瀏覽器更快的解析渲染,咱們須要考慮這幾點:webpack

  1. CSS嵌套層級不要太深,不超過3級,避免在最內層使用通配選擇器。參考關於 CSS 選擇器性能
  2. JS腳本不要太複雜,考慮輕量化架構,下降JS複雜性,減小解析時間,儘可能不要引用複雜的第三方腳本。
  3. 按需加載模塊,按需打包,首頁僅僅加載和執行和首屏相關的腳本。其餘腳本延遲加載執行。
  4. 考慮依賴的第三方模塊是否是必須,需不須要精簡。
  5. 打包優化,code split 和 tree shaken。經常使用webpack和rollup的優化。
  6. 用戶交互相關事件綁定(好比頁面scroll,用戶左右滑動等),添加參數{passive:true},減小瀏覽器事件等待。由於這些事件屬於可阻止事件,瀏覽器不知道用戶會不會阻止,因此須要等待js執行,而後再作響應。添加passive參數,就告訴瀏覽器不用等待了。
  7. IOS8之後的ios支持wkwebview,可是不少app以前用的仍是uiwebview,建議轉換成wkwebview,得到性能的提高(UIwebview在執行JS時會阻塞UI渲染進程,WKwebview不會)。

介紹一下code split的方案: react-loadable

// 未處理
import OtherComponent from './OtherComponent';
const MyComponent = () => (
  <OtherComponent/>
);
// 使用react-loadable按需加載
import Loadable from 'react-loadable';
const LoadableOtherComponent = Loadable({
  loader: () => import('./OtherComponent'),
  loading: () => <div>Loading...</div>,
});
const MyComponent = () => (
  <LoadableOtherComponent/>
);

這個也能夠在打包工具統一配置,不用每一個模塊都本身寫。

只有瀏覽器儘快渲染出來,用戶才能儘快的能夠交互。

數據埋點

上面咱們梳理了加載到解析渲染過程應該作的事情,那麼若是你這些都作好了,發現網頁表現依然不盡人意,那麼你就要考慮作一下數據埋點。其實數據埋點在企業項目中也是必不可少的,和性能體驗優化構成閉環。經過數據來發現頁面性能和體驗的問題,更有針對的進行解決。

事實上數據埋點分爲三類:

  1. 業務埋點,統計諸如pv、uv、點擊率、流失率、轉化率等
  2. 大數據埋點,統計與用戶行爲相關信息,好比那個用戶點擊了那個商品,上報用戶id和商品id,方便後臺分析用戶和商品的關係,能夠用作大數據分析,推薦算法來爲用戶推薦商品。

工程埋點,統計工程上的數據信息,好比頁面秒開率,dns時間等,也就是咱們上節課總結的性能和體驗數據指標

資源緩存

這一節咱們單獨介紹緩存,是的,利用好緩存能夠解決不少問題,包括頁面加載和渲染的問題都能獲得很好的優化。

常見的h5緩存方案有不少種,

一般,與頁面加載性能相關的,有下面幾種緩存,

(1)MemoryCache

MemoryCache,資源存放在內存中,通常資源響應回來就會放進去,頁面關閉就會釋放。內存存取性能可達磁盤緩存性能的100倍,但這還不是MemoryCache的最大優點,MemoryCache最大的優點是離排版渲染引擎很是近,能夠直接被讀取,甚至無需通過線程轉換。在真實的頁面訪問過程當中,獲取資源的時間,磁盤IO僅僅是其中的一部分,更多的時間每每消耗在各類線程拋轉。

(2)ClientCache

ClientCache,客戶端緩存,好比,手淘裏的ZCache(離線壓縮包緩存),本質上屬於磁盤緩存。這類Cache的優勢是能以相對可控的方式讓資源提早緩存在磁盤,但它也有一系列的成本。好比,它須要一套服務器與客戶端協同的下發更新邏輯,服務器端須要管理下發,客戶端須要提早解壓縮。咱們可能以爲提早解壓並非什麼弱點,但若是有一千個離線包,這個問題就比較嚴重了,若是不提早解壓,就沒法保證首次訪問性能,若是提早解壓會讓IO很是繁忙,可能會形成客戶端打開時嚴重卡頓。

(3)HttpCache
img_5c24edc7b7635.png
img_5c24edf5b13dd.png
HttpCache,是歷史比較悠久的緩存,它利用標準的 Cache-Control 與服務器端進行協商,根據標準的規則去緩存或更新資源。它應用很是普遍,是很是有效果的一種磁盤緩存。它的缺點是徹底由瀏覽器按標準規則控制,其它端的控制力度很是弱。好比,某些被HttpCache緩存的靜態資源出問題了,一般只能是改頁面,再也不使用出問題的資源,而沒法主動清除出問題的資源。參考http請求緩存頭,HTTP協商緩存VS強緩存原理

(4)NetCache

網絡相關的Cache,通常是指DNS解析結果的緩存,或預鏈接的緩存。DNS預解析和預鏈接是很是重要的,建立一個Https鏈接的成本很是大,一般須要600ms以上,也就是說,頁面若是有關鍵資源須要全新建鏈接,秒開基本是不可能了。

(5)CDN

CDN通常是經過負載均衡設備根據用戶IP地址,以及用戶請求的URL,選擇一臺離用戶比較近,緩存了用戶所需的資源,有較好的服務能力的服務器,讓用戶從該服務器去請求內容。它能讓各個用戶的緩存共享,縮短用戶獲取資源的路徑,來提高總體的性能。

固然,還有其它很是多類型的Cache,好比,

JS相關,V8 Bytecode Cache,字節碼緩存,能極大的減小JS解析耗時,甚至能夠提高3-6倍的性能。參考:前端優化系列 – JS解析性能分析
渲染相關,圖片解碼數據緩存,是一塊很是大的內存緩存,約100M,能保證頁面滾動過程能夠實時獲取到圖片解碼數據,讓滾動很是流暢。
頁面相關,頁面緩存,Safari的PageCache,Firefox的Back-Forward Cache,UC瀏覽器的WebViewCache,都是同樣性質的緩存,將整個執行過的頁面保存在內存。標準的頁面緩存,進入的條件很是苛刻,大部分狀況都沒法進入,並且在前進後退的場景才容許使用。

緩存優化實例

前面介紹了不少理論層面的內容,咱們接下來介紹一些實踐優化案例。

(1)預置資源進MemoryCache

在頁面的onPageFinished的回調裏面去檢查是否有資源能夠預置,若是有,就經過相關接口把資源設置進內核的MemoryCache。咱們並不知道用戶即將會訪問什麼頁面,若是把大量的資源都預置進內存,而用戶卻沒有使用,那就會形成浪費。另外,資源在內核內存,僅僅是加快了資源的加載速度,頁面的首屏包含很是多很是複雜的流程,某個流程的加速並不必定能帶來總體性能的提高,好比,非關鍵的JS放在內存,可能就會先於一些關鍵JS被提早執行,反而讓首屏更慢。因此,選擇放那些資源進內存也是很是有講究的,能預置的資源通常是 很是關鍵的更新頻率較低的少許公共基礎資源。

對於通常公司來講,沒有能力本身定製webview渲染的內核,能夠看下系統默認webview內核有沒有這樣的接口來實現操做MemoryCache預置數據的能力。

(2)預加載資源進HttpCache

預置資源進內存,對加載性能的提高是最明顯的,但成本也是最大的,會佔用用戶手機寶貴的內存資源。另一種預置資源的思路是,提早經過內核去預加載一些資源,資源加載回來以後就直接保存在標準的HttpCache。資源在HttpCache和在客戶端緩存(好比,手淘ZCache)的性能差異不大。但若是資源不能放進ZCache,經過這種方式提早放到HttpCache,也是一種優化思路。

(3)使用WebViewCache極速切換頁面

H5頁面的加載流程是很是重的一套流程,即便同一個頁面屢次重複訪問,也須要走比較完整的流程,耗時極長,這與用戶的指望是不符的,一般用戶指望訪問過的頁面就能快速展示出來。在一些特定的場景,H5也是能夠作到極速展示的,好比,前進後退。其它的場景,好比頁內幾個TAB切換,是否也能夠用上這類緩存呢?也是能夠的。原理上也是比較簡單的,在頁面首次訪問時,會將排版渲染好的頁面放進WebViewCache裏,WebViewCache是存儲完整頁面的一塊內存。

用戶再次訪問該頁面時,會將WebViewCache內存中的完整頁面讀取出來,直接繪製展示,而無需再進行加載解析排版渲染等流程,從而達到極速打開的效果。

除了內核提供WebViewCache基礎技術以外,前端也須要與內核進行必定的交互,好比,經過JSAPI查詢當前頁面是否在WebViewCache,若是在則返回它在WebViewCache列表的位置,而後前端就可使用JSAPI去跳轉到相應位置的頁面,內核就把頁面從內存讀取和展示出來。使用此類技術,頁面通常能在500ms左右徹底展示出來,具備很是好的用戶體驗。

固然這個也是須要瀏覽器內核提供這種能力,若是公司有本身的內核開發團隊,能夠作到定製。

(4)前端使用LocalStorage緩存HTML文檔

當前前端渲染很是流行,頁面大部分的邏輯都會由前端JS去執行,JS執行完纔會生成完整的HTML文檔,而JS執行的成本是很是大的,JS執行時間可能佔據首屏時間的50%,有些甚至能達到80%。那麼,咱們有沒有可能將JS執行生成的完整HTML文檔緩存起來呢,下次訪問時直接使用已緩存的頁面,而無需重複執行JS?這也是能夠的原理上也不復雜,首次訪問頁面時,JS執行完以後會生成完整的HTML文檔,咱們將HTML文檔緩存到LocalStorage裏面。
img_5c19d9a56a107.png
在後續的訪問中,咱們優先從LocalStorage裏面讀取HTML文檔,解析排版渲染頁面,而無需JS執行去生成頁面,讓頁面展示速度獲得極大的提高。
img_5c19d9ba95442.png
這種方案的關鍵在於前端可以實現一套DOM-Diff更新的機制,在從LocalStorage讀取HTML頁面的同時,前端還會發起請求去更新HTML文檔,在新的HTML文檔回來以後,會和舊的文檔進行Diff,針對Diff來進行局部更新,這樣能保證頁面獲得及時的更新。

(5) service worker
sw-events.png
參考使用 Service Workers提高體驗,這裏附帶介紹下這個方案,目前service worker 只有在android的webview中可用,ios還不支持。咱們經過先註冊一個serviceworker服務,指定哪些資源和數據須要存儲,而後下次請求頁面會自動激活這個service worker,頁面請求時會先從service worker中返回緩存的數據。固然service worker中須要本身處理版本和維護數據更新。

經常使用工具

Code coverage,檢測哪些代碼執行到了,哪些沒有。支持Javascript和CSS。
img_5bfd663fd2198.png
webpagetest:https://webpagetest.org/
WX20181112-091500@2x.png
Chrome Lighthouse 插件:https://chrome.google.com/web...
WX20181112-091418@2x.png

本文部分圖片和參考內容來自於網絡,若有侵權,請聯繫我刪除

相關文章
相關標籤/搜索