指標:
- 頁面是否能正常訪問?首次內容繪制(First Contentful Paint, FCP)
- 頁面內容是否有用?首次有效繪制(First Meaningful Paint,FMP)
- 頁面功能是否可用?可交互時間(Time to Interactive,TTI)
頁面內容是否有用比較主觀。難以規范化數值衡量。
對於小程序
- 首次內容繪制 FCP = 白屏加載結束 = 頁面開始展示的時間點 - 開始請求時間點(performance.Timing.navigationStart)。在 H5 中就是開始 head 的末尾插入 script,它的執行作為頁面開始展示時間節點。在小程序里可以用 onLoad 生命周期回調觸發時間替代吧?
- 首次有效繪制 FMP = 首屏渲染完成 = 首屏幕最慢的圖片加載完成時間(或者無圖片就 performance.timing.domContentLoadedEventStart)- performance.Timing.navigationStart。
- 可交互時間 TTI = performance.timing.domContentLoadedEventStart(頁面元素加載完畢,圖片未必加載完,但可以滾動,搜索操作)時間- performance.Timing.navigationStart。
小程序官方性能指標:
- 首屏時間不超過 5 秒
- 渲染時間不超過 500 ms
- 每秒調用 setData 的次數不超過 20 次
- setData 的數據再 JSON.stringify 后不超過 256 kb
- 頁面 WXML 的節點少於 1000 個,節點樹深度少於 30 層,子節點數不大於 60 個。
- 所有網絡請求都在 1s 內返回結果;
小程序的最終渲染載體仍舊是瀏覽器內核,而非原生客戶端。但給予性能考慮,它啟用了雙線程模型。
webview 線程負責視圖渲染,邏輯層負責執行 JS 代碼。邏輯層通過通過 setData 來傳輸給視圖層數據渲染。然而任何線程間數據傳遞都有延時,所以通信是異步的,視圖層沒法控制具體的 DOM。
小程序加載流程
- 准備運行環境。啟動雙線程環境,預先加載小程序基礎庫(包括 Webview 的基礎庫和 AppService 的基礎庫,前者注入到試圖中,后者注入到邏輯層)。
- 下載小程序代碼包。初次啟動需要下載編譯后的代碼包到本地,之后就會緩存代碼。小程序代碼表最終是經過 GZIP 壓縮放在 CDN 上的。
- 加載小程序代碼包。下載完后執行代碼包,此階段內主包內所有頁面 JS 都會被執行。並做頁面注冊,會調用 JS 里的 Page 構造器來初始化 JS 的數據、方法。
- 初始化小程序首頁。代碼包加載完畢,基礎庫尋找到首頁,初始化頁面實例,並傳遞信息給視圖層。視圖層結合 WXML、WXSS,初始數據來渲染頁面。
若是首次內容繪制 FCP 時間長,怎么優化?
這通常都是加載的代碼包大引起的,則減小代碼體積
- 減少小程序代碼包體積,剔除掉冗余代碼,可以使用打包工具做 tree-shaking(需要創建 webpack 化的小程序項目)。還有像是 moment 有替代方案,去掉這個也能讓小程序變小。再開啟 GZIP 壓縮。
- 分包預下載。小程序提供的能力,可以在進入某個頁面預下載接下來可能會用到的分包,避免切頁面白屏。
- 部分頁面 H5 化。小程序提供了 web-view 組件,可以直接引用外部 H5 頁,一方面這也能減少小程序包的體積,一方面讓經常需要高度變化的內容擴展更改的更便捷。
若是首次有效繪制 FMP 時間長,怎么優化?
骨架屏
- 上骨架屏吧,這是縮短 FMP 首屏渲染時間的重要手段,可以減緩用戶的焦慮感。
圖片優化
- 減少靜態資源文件的大小,使用 webp,合適的圖片壓縮工具,合理的剪裁降質來減少圖片體積
- CDN 圖床加快圖片的獲取速度。
- 降級加載大圖資源。對於真的需要一張渲染很大的一張圖片,可以預先使用高度壓縮模糊的圖片占位替代(給一個量化標准是 200kb以內),等到原圖加載完畢再轉移到真實節點上渲染。只需要一個
display: none的 image 標簽,就可以做到只加載圖片資源,但不渲染。
接口拉取優化
- 接口本地緩存;開啟數據預拉取,小程序啟動時,微信服務器可以代理小程序客戶端獲取 HTTP 請求,當小程序加載完后調用 wx.getBackgroundFetchData 從本地緩存拿數據。
- 跳轉時預拉取數據。從上個頁面跳轉到下一個頁面,環境初始化以及頁面實例化的工作需要耗時 300~400 ms,所以可以不在 onLoad 鈎子觸發再發起網絡請求,取而代之可以在上一個頁面 wx.navigateTo 調用前獲取接口,並存儲在全局 Promise 對象中,等待下一個頁面加載完成再從 Promise 對象中讀取數據。這也是雙線程模型的優勢,不同頁面跳轉不需要銷毀全局對象。
- 非關鍵渲染數據延遲請求。
- 請求合並。wx.request(HTTP連接)的最大並發數量是 10 個,請求頻繁的時候可以檢查是否可以合並一段時間內的請求數據,做一個請求發送給服務器。
若是可交互時間 TTI 長,怎么優化
wx.navigateTo 跳轉時
- 准備新的 webview 線程環境,初始化基礎庫
- 從邏輯層到視圖層初始數據通信
- 視圖層結合 WXML、WXSS,初始數據來渲染頁面。
切換時主要性能損耗在 數據通信 和 節點數創建/更新 上
- 降低線程通信頻次
- 減少通信數據量
- 減少 WXML 節點數量。
- 減少事件數量及觸發次數
做法有
- 合並 setData 調用
- 只把與渲染渲染相關的數據放到 data 中。
- 應用層數據 diff。只要調用 setData 那就必然會有視圖層渲染(當然視圖層自己也會合並 data 數據另找時間渲染),那么開發者自己可以比對兩次數據是否有變化,有變化再 setState
- 去掉不必要的事件綁定。事件信息也需要從視圖層通信反饋給邏輯層,所以盡量減少不必要的事件綁定,尤其是 onPageScroll 里面還 setData 的這種。
- 適當的組件顆粒度。嵌套層級不能太深,組件數量和小程序代碼包大小正相關。但也不能往一個組件上掛載太多的東西。
內存占用高?怎么優化
內存占用高,內存空間會被系統銷毀,或者被微信客戶端主動回收,導致小程序 Crash
小程序提供了監聽內存不足告警的事件 API:wx.onMemoryWarning,可以告知開發者內存緊張。但開發者無法操作內存資源,頂多調用 wx.reLaunch 來清理頁面棧,重新加載頁面,來降低內存負荷。但開發者更應該仔細取收集告警信息上報到日志系統,分析並對程序做優化。
- 回收后台頁面計時器。從上一個頁面切換到下一個頁面,上一個頁面的定時器 setTimeout 和 setTimeInterval 仍舊會繼續執行,可能就會占用過多的內存資源,onHide 時清理掉,onShow 時再恢復。比如說小程序的
<swiper>組件,進入后台時仍舊繼續輪播。 - 避免頻發事件中重度內存操作。比如說導航欄吸頂效果,onPageScroll 事件回調必須使用節流函數,並在它的回調中避免使用 setData。部分場景盡量使用 IntersectionObserver API。
- 大圖,長列表優化。快速滾動長列表,被銷毀的組件可能來不及加載完。小程序提供了長列表組件,virtual List
