微信小程式效能優化方案

前端彭于晏Eddie發表於2020-12-17

微信小程式效能優化方案

微信小程式如果想要優化效能,有關鍵性的兩點:

  1. 提高載入效能
  2. 提高渲染效能

提高載入效能

首先,我們需要了解,當使用者點選小程式後小程式啟動的流程,具體詳細的流程在微信官方文件 啟動效能 中有描述,以下的篇章我們主要著重於我們能夠改變優化的環節。

小程式程式碼包準備(下載程式碼包) => 開發者程式碼注入(邏輯和渲染) => 首頁(初次)渲染

小程式程式碼包準備(下載程式碼包)

下載程式碼包提升效能的最關鍵性一點就是,控制程式碼包的大小。

至於如何控制包的大小?

  1. 分包載入
    我在另一篇部落格中具體講解了分包載入,大家可以移步去學習
    小程式分包載入實踐
  2. 程式碼重構和優化
    通過程式碼重構,降低程式碼冗餘。在使用如 Webpack 等打包工具時,要儘量利用 tree-shaking 等特性去除冗餘程式碼,也要注意防止打包時引入不需要的庫和依賴。
  3. 控制程式碼包內圖片等資源
    避免在程式碼包中包含過多、過大的圖片,應儘量採用網路圖片。
  4. 及時清理沒有使用到的程式碼和資源
    目前小程式打包是會將工程下所有檔案都打入程式碼包內,因此需要及時清理已經沒有被實際使用到的庫檔案和資源也。

開發者程式碼注入

  1. 減少啟動過程的同步呼叫
    在小程式啟動流程中,會注入開發者程式碼並順序同步執行 App.onLaunch, App.onShow, Page.onLoad, Page.onShow。
    在小程式初始化程式碼(Page,App 定義之外的內容)和啟動相關的幾個生命週期中,應避免執行復雜的計算邏輯或過度使用Sync結尾的同步API,如 wx.getStorageSync,wx.getSystemInfoSync 等。
    對於 getSystemInfo, getSystemInfoSync 的結果應進行快取,避免重複呼叫。

  2. 使用懶注入
    通常情況下,在小程式啟動時,啟動頁面所在分包和主包(獨立分包除外)的所有JS程式碼會全部合併注入,包括其他未訪問的頁面以及未用到自定義元件,造成很多沒有使用的程式碼注入到小程式執行環境中,影響注入耗時和記憶體佔用。

自基礎庫版本 2.11.1 起,小程式支援僅注入當前頁面需要的自定義元件和當前頁面程式碼,以降低小程式的啟動時間和執行時記憶體。開發者可以在 app.json 中配置:

{
  "lazyCodeLoading": "requiredComponents"
}

頁面渲染優化

  1. 提前首屏資料請求

資料預拉取:能夠在小程式冷啟動的時候通過微信後臺提前向第三方伺服器拉取業務資料,當程式碼包載入完時可以更快地渲染頁面,減少使用者等待時間,從而提升小程式的開啟速度。

週期性更新:在使用者未開啟小程式的情況下,也能從伺服器提前拉取資料,當使用者開啟小程式時可以更快地渲染頁面,減少使用者等待時間。

  1. 骨架屏
    在頁面資料未準備好時(如需要通過網路獲取),儘量避免展示空白頁面,應先通過骨架屏展示頁面的大致結構,請求資料返回後在進行頁面更新,以提升使用者的等待意願。

  2. 快取請求資料
    小程式提供了wx.setStorage、wx.getStorage等讀寫本地快取的能力,資料儲存在本地,返回的會比網路請求快。可以優先從快取中獲取資料來渲染檢視,等待網路請求返回後進行更新。

  3. 精簡首屏資料
    延遲請求非關鍵渲染資料,與檢視層渲染無關的資料儘量不要放在 data 中,加快頁面渲染完成時間。

提升渲染效能

setData工作原理

小程式的檢視層目前使用 WebView 作為渲染載體,而邏輯層是由獨立的 JavascriptCore 作為執行環境。在架構上,WebView 和 JavascriptCore 都是獨立的模組,並不具備資料直接共享的通道。當前,檢視層和邏輯層的資料傳輸,實際上通過兩邊提供的 evaluateJavascript 所實現。即使用者傳輸的資料,需要將其轉換為字串形式傳遞,同時把轉換後的資料內容拼接成一份 JS 指令碼,再通過執行 JS 指令碼的形式傳遞到兩邊獨立環境。

而 evaluateJavascript 的執行會受很多方面的影響,資料到達檢視層並不是實時的。

每呼叫一次setData, 都是邏輯層向渲染層的一次通訊,這個通訊還不是直接傳給webView, 而是通過走了native層,通訊的開銷很大。

渲染層收到通訊後,還需要重新渲染出來,所以,一次setData帶來兩次開銷:通訊的開銷 + webview更新的開銷。

優化方法

  1. 減少setData的資料量
    如果一個資料不能會影響渲染層,則不用放在setData裡面

  2. 合併setData的請求,減少通訊的次數

  3. 列表的區域性更新

在一個列表中,有n條資料,當前需求為實現點贊效果。

首先,我們可以先展示效果,再進行非同步請求,只在請求失敗的情況下提示。
其次,我們可以採用佈局重新整理,根據點贊資料的id和index分別獲取資料以及更新資料

this.setData({
    list[index] = newList[index]
})
  1. 小心後臺頁面的js

小程式中可能有n個頁面,所有的這些頁面,雖然都擁有自己的webview(渲染層), 但是卻共享同一個js執行環境。也就是說,當你跳到了另外一個頁面(假設是B頁面),本頁面(假設是A頁面)的定時器等js操作仍在進行,並且不會被銷燬,並且會搶佔B頁面的資源。

在h5的環境中,當我們跳轉到其他頁面,老頁面的js環境會被自動銷燬,定時器什麼都被銷燬掉了,因此我們不需要關心老頁面中,還有哪些js程式碼可能還會執行。但是在小程式中,我們必須手動的“清理”掉這樣的程式碼。

  1. 小心onPageScroll

pageScroll 事件,也是一次通訊,是webview層向js邏輯層的通訊。這個事件很容易被頻繁呼叫,開銷較大,並且假如回撥函式有複雜的setData的話, 效能就會很差了。

  1. 合理使用小程式元件
    自定義元件的更新只在元件內部進行,不會影響頁面其他元素。因為各個元件具有獨立的邏輯空間、資料、樣式環境及 setData 呼叫。
    基於自定義元件的 Shadow DOM 模型設計,我們可以將頁面中一些需要高頻執行 setData 更新的功能模組(如倒數計時、進度條等)封裝成自定義元件嵌入到頁面中。
    當這些自定義元件檢視需要更新時,執行的是元件自己的 setData,新舊節點樹的對比計算和渲染樹的更新都只限於元件內有限的節點數量,有效降低渲染時間開銷。

  2. 圖片懶載入

渲染頁面時,只渲染出現在檢視範圍內的元素。

IntersectionObserver
微信提供了IntersectionObserver物件,用於推斷某些節點是否可以被使用者看見、有多大比例可以被使用者看見

常規的做法是,通過getBoundingClientRect()獲取元素的位置,然後與頁面滾動位置比較,如果出現在檢視內,就將img顯示。這種方式有2個問題
getBoundingClientRect()方法呼叫本身容易引起頁面重排
監聽滾動事件本身就頻繁觸發,雖然可以通過節流的方式來減少,但還是容易增加無謂程式碼處理

<img class="img-{{index}}" wx:for="{{data}}" wx:if="{{item.imgShow}}" wx:key="index"></img>
let data = list;
data.forEach((item,index)=>{
    this.createIntersectionObserver().relativeToViewport.observe(`.img-${index}`,res=>{
        if (res.intersectionRatio > 0){
            this.setData({
                item.imgShow:true
            })
        }
    })
})

intersectionRatio值大於0,說明元素出現在檢視中了,顯示圖片元件。

相關文章