前端性能优化实践 | 百度APP个人主页优化
性能是每個前端工程師都應該關注的話題,通用的優化手段已有許多文章和實踐,就不再贅述,本篇以百度 App 個人主頁為例,聊聊針對業務特點進行的一些性能優化實踐。適用于:傳統意義的優化手段能用的都用了:打包拆包,縮減體積和 HTTP 請求數、CDN 和按需加載等,但性能方面仍不太理想。
01優化三部曲
下面介紹下我們的優化三部曲,這也是所有優化項目的基本步驟:
第一步:定義指標,建設報表
優秀方案的制定首先需要準確的數據做支撐。
一般來說,前端性能指標包括 DOM ready、First Contentful Paint、白屏、首屏、用戶可操作時間、onload 時間等,在實際中需要結合業務本身的特點進行定義,一般通用的指標定義并不能體現用戶在當前業務下的真實體驗。
個人主頁是在百度 App 客戶端內的 web 頁面,有 hybrid 版(使用 file 協議直接加載本地 HTML 和 JS、CSS)和 web 版(打開一個 web URL)兩種不同的打開方式。
首先,我們了解一下個人主頁頁面的結構:
頭部區域展示當前作者的個人信息,tab 區域則是作者創作產生的內容。頁面中所有數據均為異步獲取。
打開個人主頁需要經歷的過程可簡化成以下幾個:
其中耗時可劃分為端(Native)耗時、網絡和 server 耗時、前端渲染耗時三大部分:
根據以上過程,我們制定了定義指標的原則:
- 主頁頁面展現的用戶數據,是頁面內 JS 請求數據后的異步渲染。因此首屏定義為:頭部區域 和 tab 列表第一屏數據渲染完成(用戶真正可見,也即用戶可操作時間)
- 主頁是由 san 搭建的 SPA 頁面,HTML 上同步的 DOM并沒有真實內容出現,在首屏用戶數據返回之前,頁面均顯示為 loading 態。因此白屏的定義為:頁面 DOM掛載上內容(用戶首次看到頁面不再空白的時間)
- 由于在端內,iOS 和 安卓 onload 事件觸發的時機不同,iOS上資源加載會阻塞頁面渲染,因此主頁中針對 iOS 進行了調整,使用 rAF 使 iOS 在 JS 開始執行時即觸發onload,而安卓在首屏需要的圖片、jsonp 等資源全部 load 完成后觸發,因此該項指標主要用作輔助作用。
在報表建設的過程中,結合主頁的業務形態(在 Android、iOS 雙端均有 hybrid 版和 web 版兩種)以及指標定義的含義,對整個過程的階段盡可能細化,方式如下:
- 將主頁的三種版本進行分組打點:hybrid 版,hybrid 混合 web 版,web版。即可避免數據干擾,又可通過控制上線時間,來進行實驗對比
- 添加系統、端內外、起始點作為篩選條件,排除不同的使用條件帶來的數據差異,有助于縮小范圍,定位分析
- 根據主頁的執行流程,將耗時流程細分,進一步定位問題,整個階段細分為:端耗時(從點擊到解析頁面 head 頂部)、同步 HTML 內各 JS引用階段耗時、數據請求耗時、頁面內端能力及各組件生命周期到首屏耗時(耗時部分在實際優化中逐步細化分析)
最終得到每個階段的詳細劃分:
補充說明
- 端到端打點即以用戶進行操作的時刻為起始點來記錄數據,涵蓋了從用戶發起點擊操作開始到頁面完全展現之間的客戶端耗時、網絡耗時、server耗時、前端各個階段耗時的完整流程打點。 其中,起始時間戳由上一個頁面在用戶操作時進行記錄,并透傳給個人主頁;前端在每次發起網絡請求時記錄當前時間戳,接口的返回值中傳回后端處理接口的耗時,二者的差值即 HTTP 連接的耗時。本次采用前端計算上報、平臺展示的方案,打點前端控制靈活且迭代快。
- 端耗時部分前期僅能計算從上一個頁面點擊到解析頁面頂部的整體時間。
第二步:數據分析,提出優化方向
經過第一步,拿到穩定的數據及頁面各階段耗時,分析并提出解決方案。
圖中性能數據可以看出主要耗時階段:端耗時、引入主 profile.js 到 js 內部開始耗時、首屏接口耗時、頁面數據處理和渲染耗時,針對主要耗時階段,優化分為以下幾個方面:
- 針對端耗時,前端配合端查找優化點
- 針對首屏接口耗時,前端聯合 server 進行接口優化
- 針對 JS 內部耗時,前端進行自身代碼優化
第三步:著手優化,逐步完善
主頁入口較多,需要兼容不同入口情況以及歷史遺留,個人主頁業務基本情況如下:
1.頁面為 SPA 模式,但業務復雜,代碼總體積較大
2.hybrid 版 首屏需要的資源由兩個接口返回,且兩個接口存在依賴,在前端串行執行
3.web 版頂部用戶數據使用同步數據,依賴的 tab 接口與 hybrid 相同
4.hybrid 版 的特點:
- hybrid 版 與 web 版 使用不同方式編譯的同一套代碼,但 web 版上第一個接口是同步的,數據隨著 HTML 模板一起返回,而hybrid 中所有接口均是異步
- 使用的場景更多
- 采用 file 協議加載 HTML 模板,用 jsonp 的方式請求后端數據接口
按照前端代碼、工程化、server 端、客戶端 native 框架四個方向分別針對性制定優化方案,以下主要介紹前端可控的代碼和工程化兩個方面。
02前端代碼優化
提前觸發iOS onload
方法:使用 rAF 嵌套 setTimeout 提前觸發 onload 事件,解決 iOS 資源加載阻塞頁面顯示的情況
收益:用戶可見的首屏時間不受 load 阻塞
減少首屏依賴
首屏時間可反映出用戶對頁面速度的感受,首屏所依賴的行為越多,就意味著用戶需要等待的時間越長。因此,在性能優化中需要盡可能地減少在首屏前執行的操作、后置一些非必要的操作,可以在某種程度上提升用戶體驗。
經過一段時間的數據收集分析和代碼 review,我們發現一些可以改進的地方:
結合以上發現,對代碼進行了如下調整:
- 調整與 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)
優化效果總結
優化后 JS 初始化耗時減少,首屏數據 jsonp 請求使用內聯的極簡代碼包在頁面準備完畢前就發出,在 jsonp 得到返回值前并行加載頁面需要的其他 CSS 和 JS 資源;App 掛載和頁面 runtime 初始化的時間提前,首屏數據回來后可以立馬處理并渲染數據,而不被其他的一些操作占用寶貴的時間;打點等請求后置,首屏完畢前讓 JS 專注于數據和渲染,同時騰出帶寬加載圖片等用戶可見的資源。優化后的流程可用下圖表示:
03總結
工欲善其事,必先利其器。在著手進行優化前,有大量的時間花在了選取數據參考點、收集數據上,通過反復的 code review、實驗、業務邏輯推敲來確保每個關鍵指標反映的都是真實可信的數據。本文僅僅提供一種從數據著手的優化點分析方法,列舉的優化方法與實際業務密不可分,并不具有太強的普適性,希望能給大家在解決瓶頸的道路上帶來一點不一樣的思路。
作者 @潘銘
文章看完,還不過癮?
更多精彩內容歡迎關注百度開發者中心公眾號
總結
以上是生活随笔為你收集整理的前端性能优化实践 | 百度APP个人主页优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工程师必知的代码重构指南
- 下一篇: 正式发布!中国首个LF Edge捐赠项目