前端性能優化實踐 之 百度App我的主頁優化

前言

性能是每一個前端工程師都應該關注的話題,通用的優化手段已有許多文章和實踐,就再也不贅述,本篇以百度App我的主頁爲例,聊聊針對業務特色進行的一些性能優化實踐。前端

適用於:傳統意義的優化手段能用的都用了:打包拆包,縮減體積和 HTTP 請求數、CDN和按需加載等,但性能方面仍不太理想。web

定義指標,建設報表

優秀方案的制定首先須要準確的數據作支撐。npm

通常來講,前端性能指標包括DOM readyFirst Contentful Paint白屏、首屏、用戶可操做時間、onload時間等,在實際中須要結合業務自己的特色進行定義,通常通用的指標定義並不能體現用戶在當前業務下的真實體驗。json

我的主頁是在百度App客戶端內的web頁面,有 hybrid版(使用file協議直接加載本地HTML和JS、CSS)和web版(打開一個web URL)兩種不一樣的打開方式。後端

首先,咱們瞭解一下我的主頁頁面的結構:瀏覽器

我的主頁頁面的結構.jpg

頭部區域展現當前做者的我的信息,tab區域則是做者創做產生的內容。頁面中全部數據均爲異步獲取。 緩存

打開我的主頁須要經歷的過程可簡化成如下幾個:性能優化

打開我的主頁須要經歷的過程.jpg

其中耗時可劃分爲端耗時、網絡和server耗時、前端渲染耗時三大部分:微信

耗時.jpg

根據以上過程,咱們制定了定義指標的原則:babel

  • 主頁頁面展示的用戶數據,是頁面內 JS 請求數據後的異步渲染。所以首屏定義爲:頭部區域 和 tab列表 第一屏數據渲染完成(用戶真正可見,也即用戶可操做時間)
  • 主頁是由san搭建的SPA頁面,HTML 上同步的 DOM 並無真實內容出現,在首屏用戶數據返回以前,頁面均顯示爲 loading 態。所以白屏的定義爲:頁面DOM掛載上內容(用戶首次看到頁面再也不空白的時間)
  • 因爲在端內,iOS 和 安卓 onload 事件觸發的時機不一樣,iOS上資源加載會阻塞頁面渲染,所以主頁中針對 iOS 進行了調整,使用 rAF 使 iOS 在 JS開始執行時即觸發 onload,而安卓在首屏須要的圖片、jsonp 等資源所有 load 完成後觸發,所以該項指標主要用做輔助做用。

 .jpg

在報表建設的過程當中,結合主頁的業務形態(在Android、iOS雙端均有hybrid版和web版兩種)以及指標定義的含義,對整個過程的階段儘量細化,方式以下:

  • 將主頁的三種版本進行分組打點:hybrid版,hybrid混合web版,web版。便可避免數據干擾,又可經過控制上線時間,來進行實驗對比
  • 添加系統、端內外、起始點做爲篩選條件,排除不一樣的使用條件帶來的數據差別,有助於縮小範圍,定位分析
  • 根據主頁的執行流程,將耗時流程細分,進一步定位問題,整個階段細分爲:端耗時(從點擊到解析頁面head頂部)、同步HTML內各JS引用階段耗時、數據請求耗時、頁面內端能力及各組件生命週期到首屏耗時(耗時部分在實際優化中逐步細化分析)

最終獲得每一個階段的詳細劃分:

每一個階段的詳細劃分.jpg

補充說明:

  • 端到端打點即以用戶進行操做的時刻爲起始點來記錄數據,涵蓋了從用戶發起點擊操做開始到頁面徹底展示之間的客戶端耗時、網絡耗時、server耗時、前端各個階段耗時的完整流程打點。
    其中,起始時間戳由上一個頁面在用戶操做時進行記錄,並透傳給我的主頁;
    前端在每次發起網絡請求時記錄當前時間戳,接口的返回值中傳回後端處理接口的耗時,兩者的差值即HTTP鏈接的耗時。
    本次採用前端計算上報、平臺展現的方案,打點前端控制靈活且迭代快。
  • 端耗時部分前期僅能計算從上一個頁面點擊到解析頁面頂部的總體時間。

數據分析,提出優化方向

通過階段一,拿到穩定的數據及頁面各階段耗時,分析並提出解決方案。

性能數據-優化前.jpg

圖中性能數據能夠看出主要耗時階段:端耗時、引入主profile.js到js內部開始耗時、首屏接口耗時、頁面數據處理和渲染耗時,針對主要耗時階段,優化分爲如下幾個方面:

針對端耗時,前端配合端查找優化點
針對首屏接口耗時,前端聯合server進行接口優化
針對JS內部耗時,前端進行自身代碼優化

着手優化,逐步完善

主頁入口較多,須要兼容不一樣入口狀況以及歷史遺留,我的主頁業務基本狀況以下:

  • 頁面爲 SPA 模式,但業務複雜,代碼整體積較大
  • hybrid版 首屏須要的資源由兩個接口返回,且兩個接口存在依賴,在前端串行執行
  • web 版頂部用戶數據使用同步數據,依賴的tab接口與hybrid相同
  • hybrid版 的特色:

    • hybrid版 與 web版 使用不一樣方式編譯的同一套代碼,但 web版上第一個接口是同步的,數據隨着 HTML 模板一塊兒返回,而 hybrid 中全部接口均是異步
    • 使用的場景更多
    • 採用 file 協議加載 HTML 模板,用 jsonp 的方式請求後端數據接口

按照前端代碼、工程化、server端、客戶端native框架四個方向分別針對性制定優化方案,如下主要介紹前端可控的代碼和工程化兩個方面。

前端代碼

提早觸發iOS onload

  • 方法:使用 rAF 嵌套 setTimeout 提早觸發 onload 事件,解決 iOS 資源加載阻塞頁面顯示的狀況
  • 收益:用戶可見的首屏時間不受 load 阻塞

減小首屏依賴

首屏時間可反映出用戶對頁面速度的感覺,首屏所依賴的行爲越多,就意味着用戶須要等待的時間越長。所以,在性能優化中須要儘量地減小在首屏前執行的操做、後置一些非必要的操做,能夠在某種程度上提高用戶體驗。

通過一段時間的數據收集分析和代碼 review,咱們發現一些能夠改進的地方:

  1. 在首屏前的一段邏輯裏,JS 初始化一些數據時一次性調用了多個 native 提供的方法(端能力),致使端能力執行耗時 80分位值 遠超理論值;
  2. SPA 頁面的最外層組件 App 在首個接口的數據返回後才進行掛載。對 web版頁面來講,首個接口是同步的,所以 App 在接口後掛載影響不大;但對使用場景更多的 hybrid版 來講,頁面的首屏至少須要兩個接口,而全部接口請求均爲異步,首個接口返回以前足以處理不少頁面必要的邏輯,App 的掛載時機就顯得很是不合適。
  3. 頁面上埋了不少打點,除pv外其餘可能是頁面上一些小組件的展示打點,在首屏以前頻繁發送打點請求擠佔了首屏中圖片的加載時間。

結合以上發現,對代碼進行了以下調整:

  • 調整與native端能力調用的執行順序,首屏必要的留下,其餘的後置,下降端能力執行耗時
  • 優化必要的代碼信息(例如:我的主頁從頭用到尾的 runtime)初始化邏輯
  • 最外層 App 組件掛載不依賴接口數據,頁面提早進行初始化,接口數據並行請求,異步渲染
  • 調整代碼執行邏輯,關鍵邏輯移至 store,提早執行
  • 頁面內部分打點等邏輯後置,減小頁面掛載執行時間
    一波操做下來,得到了80分位100ms+的收益。

首屏接口合併

上文有提到hybrid首屏須要在前端串行執行兩個異步網絡接口。從統計到的性能數據上看,在調用接口到拿到接口數據的過程當中,耗時最長的是創建網絡鏈接這個階段,兩個接口合併成一個接口,首屏時間上至少能夠節省一次創建網絡鏈接的時間(我的主頁作到了110ms+)。固然,接口合併也須要考慮server端的平響,考慮可能會犧牲的一丟丟白屏時間。

首屏接口前置

做爲一個標準的 SPA 頁面,我的主頁頁面上幾乎全部的邏輯都是在公共 js 加載完成以後纔開始執行,但 js 加載須要時間,尤爲是首次加載、本地尚未緩存時。
hybrid 版本沒有同步接口,只能在 js 加載完成以後才發出首屏的第一個請求,所以 hybrid 的版本在這裏還存在可優化空間。
已知如今主流瀏覽器可並行處理的請求一般默認在4~6個,在加載js時去拿首屏須要的數據(jsonp),串行變並行,節省下來一份兩者重疊的時間。
首屏接口前置就作了這麼一些事:
hybrid 打包時內聯了一個體積儘量小的極簡代碼包,去取首屏第一個接口的數據,完成後存入全局變量並以事件的形式派發出來。因爲 iOS 部分場景中首次請求創建網絡鏈接的耗時較長,順便使用 native 端能力代替 jsonp,首屏接口前置中iOS收益在260ms+,安卓60ms左右

工程化

工程化上進行的優化主要是在打包上下功夫,打包影響加載 JS、CSS 等資源的 http 耗時,在相同的條件下,包體積越小、請求次數越少,資源加載速度就越快。

打包和拆包

JS 和 CSS 資源打包合併,但須要考慮打包文件過大,單個請求耗時太長,須要結合業務場景合理拆分代碼包。
主流瀏覽器可並行處理的請求一般默認在 4~6個,可合理拆分資源包,利用並行請求縮減總體的響應時間。
經過合理劃分包來最大程度上利用瀏覽器緩存。鎖版本、保持每一個小 bundle 未發生改變時哈希值穩定,較大的 JS、CSS 和圖片等會被直接寫進硬盤緩存。例如,我的主頁根據代碼的修改頻率把 js 包拆成體積差很少大小的三個,其中 vendors 是各類 npm 依賴,版本穩定,一般不會發生改變。每次上線後用戶瀏覽器只須要從 CDN 上請求另外兩個代碼包,vendors 則使用上次還未過時的本地緩存。

現代模式(modern mode)

一般,開發時咱們使用 ES6(ES2015+) 來編寫代碼,ES6 的新特性可讓開發工做更便捷迅速但打包時須要用 Babel 進行轉換來讓咱們的代碼能運行在不支持 ES6 的瀏覽器上。轉換後的代碼會加入 polyfill,最直觀的感覺就是代碼包體積增大。modern mode 在支持原生 ES6 的瀏覽器中,js會經過 加載 ES模塊 的<script type="module"> 加載,而在不支持的瀏覽器中使用 <script nomodule> 來加載 babel 編譯後的版本,而且支持ES模塊的瀏覽器會忽略這種寫法。在支持 ES6 的瀏覽器上使用 ES6 的版本,代碼 bundle 體積更小、解析和執行的速度更快,何樂而不爲呢?
從我的主頁收集到的數據顯示,目前已有75%的場景支持 modern mode,帶來的性能收益也是很是可觀:

白屏(ms) 首屏(ms)
實驗組 1055 1913
對照組 1136 2081
收益 81 168

優化效果總結

優化後JS初始化耗時減小,首屏數據jsonp請求使用內聯的極簡代碼包在頁面準備完畢前就發出,在jsonp獲得返回值前並行加載頁面須要的其餘CSS和JS資源;App掛載和頁面runtime初始化的時間提早,首屏數據回來後能夠立馬處理並渲染數據,而不被其餘的一些操做佔用寶貴的時間;打點等請求後置,首屏完畢前讓JS專一於數據和渲染,同時騰出帶寬加載圖片等用戶可見的資源。優化後的流程可用下圖表示:

優化後的流程.jpg

優化後數據分析

性能數據-優化後.jpg

雙端首屏時間均大幅度減少,首屏請求(兩圖中橙色部分)得益於 server 同窗的接口性能優化,單單是平響就下降了80ms+;

而安卓上得益於端同窗的 hybrid 框架優化,使得 hybrid 頁面和本地js資源加載速度更快,效果更是顯著。

總結

工欲善其事,必先利其器。在着手進行優化前,有大量的時間花在了選取數據參考點、收集數據上,經過反覆的 code review、實驗、業務邏輯推敲來確保每一個關鍵指標反映的都是真實可信的數據。本文僅僅提供一種從數據着手的優化點分析方法,列舉的優化方法與實際業務密不可分,並不具備太強的普適性,但願能給你們在解決瓶頸的道路上帶來一點不同的思路。


本文做者:
前端工程師 panming


在微信-搜索頁面中輸入「百度App技術」,便可關注微信官方帳號;或使用微信識別如下二維碼,亦可關注。
微信連接.jpg

相關文章
相關標籤/搜索