性能是每一個前端工程師都應該關注的話題,通用的優化手段已有許多文章和實踐,就再也不贅述,本篇以百度App我的主頁爲例,聊聊針對業務特色進行的一些性能優化實踐。前端
適用於:傳統意義的優化手段能用的都用了:打包拆包,縮減體積和 HTTP 請求數、CDN和按需加載等,但性能方面仍不太理想。web
優秀方案的制定首先須要準確的數據作支撐。npm
通常來講,前端性能指標包括DOM ready、First Contentful Paint、白屏、首屏、用戶可操做時間、onload時間等,在實際中須要結合業務自己的特色進行定義,通常通用的指標定義並不能體現用戶在當前業務下的真實體驗。json
我的主頁是在百度App客戶端內的web頁面,有 hybrid版(使用file協議直接加載本地HTML和JS、CSS)和web版(打開一個web URL)兩種不一樣的打開方式。後端
首先,咱們瞭解一下我的主頁頁面的結構:瀏覽器
頭部區域展現當前做者的我的信息,tab區域則是做者創做產生的內容。頁面中全部數據均爲異步獲取。 緩存
打開我的主頁須要經歷的過程可簡化成如下幾個:性能優化
其中耗時可劃分爲端耗時、網絡和server耗時、前端渲染耗時三大部分:微信
根據以上過程,咱們制定了定義指標的原則:babel
頭部區域 和 tab列表 第一屏數據渲染完成
(用戶真正可見,也即用戶可操做時間)頁面DOM掛載上內容
(用戶首次看到頁面再也不空白的時間)
在報表建設的過程當中,結合主頁的業務形態(在Android、iOS雙端均有hybrid版和web版兩種)以及指標定義的含義,對整個過程的階段儘量細化,方式以下:
系統、端內外、起始點
做爲篩選條件,排除不一樣的使用條件帶來的數據差別,有助於縮小範圍,定位分析端耗時(從點擊到解析頁面head頂部)、同步HTML內各JS引用階段耗時、數據請求耗時、頁面內端能力及各組件生命週期到首屏耗時
(耗時部分在實際優化中逐步細化分析)最終獲得每一個階段的詳細劃分:
補充說明:
通過階段一,拿到穩定的數據及頁面各階段耗時,分析並提出解決方案。
圖中性能數據能夠看出主要耗時階段:端耗時、引入主profile.js到js內部開始耗時、首屏接口耗時、頁面數據處理和渲染耗時,針對主要耗時階段,優化分爲如下幾個方面:
針對端耗時,前端配合端查找優化點
針對首屏接口耗時,前端聯合server進行接口優化
針對JS內部耗時,前端進行自身代碼優化
主頁入口較多,須要兼容不一樣入口狀況以及歷史遺留,我的主頁業務基本狀況以下:
前端串行執行
hybrid版 的特色:
同步的
,數據隨着 HTML 模板一塊兒返回,而 hybrid 中全部接口均是異步
按照前端代碼、工程化、server端、客戶端native框架四個方向分別針對性制定優化方案,如下主要介紹前端可控的代碼和工程化兩個方面。
首屏時間可反映出用戶對頁面速度的感覺,首屏所依賴的行爲越多,就意味着用戶須要等待的時間越長。所以,在性能優化中須要儘量地減小在首屏前執行的操做、後置一些非必要的操做,能夠在某種程度上提高用戶體驗。
通過一段時間的數據收集分析和代碼 review,咱們發現一些能夠改進的地方:
結合以上發現,對代碼進行了以下調整:
上文有提到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 則使用上次還未過時的本地緩存。
一般,開發時咱們使用 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專一於數據和渲染,同時騰出帶寬加載圖片等用戶可見的資源。優化後的流程可用下圖表示:
雙端首屏時間均大幅度減少,首屏請求(兩圖中橙色部分)得益於 server 同窗的接口性能優化,單單是平響就下降了80ms+;
而安卓上得益於端同窗的 hybrid 框架優化,使得 hybrid 頁面和本地js資源加載速度更快,效果更是顯著。
工欲善其事,必先利其器。在着手進行優化前,有大量的時間花在了選取數據參考點、收集數據上,經過反覆的 code review、實驗、業務邏輯推敲來確保每一個關鍵指標反映的都是真實可信的數據。本文僅僅提供一種從數據着手的優化點分析方法,列舉的優化方法與實際業務密不可分,並不具備太強的普適性,但願能給你們在解決瓶頸的道路上帶來一點不同的思路。
本文做者:
前端工程師 panming
在微信-搜索頁面中輸入「百度App技術」,便可關注微信官方帳號;或使用微信識別如下二維碼,亦可關注。