內容整理於微信公開課
為什麼要做效能優化?
一切效能優化都是為了體驗優化
1. 使用小程式時,是否會經常遇到如下問題?
-
開啟是一直白屏
-
開啟是loading態,轉好幾圈
-
我的頁面點了怎麼跳轉這麼慢?
-
我的列表怎麼越滑越卡?
2. 我們優化的方向有哪些?
-
啟動載入效能
-
渲染效能
3. 啟動載入效能
1. 首次載入
你是否見過小程式首次載入時是這樣的圖?
這張圖中的三種狀態對應的都是什麼呢?
小程式啟動時,微信會為小程式展示一個固定的啟動介面,介面內包含小程式的圖示、名稱和載入提示圖示。此時,微信會在背後完成幾項工作:下載小程式程式碼包
、載入小程式程式碼包
、初始化小程式首頁
。下載到的小程式程式碼包不是小程式的原始碼,而是編譯、壓縮、打包之後的程式碼包。
2. 載入順序
小程式載入的順序是如何?
微信會在小程式啟動前為小程式準備好通用的執行環境。這個執行環境包括幾個供小程式使用的執行緒,並在其中完成小程式基礎庫的初始化,預先執行通用邏輯,儘可能做好小程式的啟動準備。這樣可以顯著減少小程式的啟動時間。
通過2,我們知道了,問題1中第一張圖是資源準備
(程式碼包下載);第二張圖是業務程式碼的注入以及落地頁首次渲染
;第三張圖是落地頁資料請求時的loading態
(部分小程式存在)
3. 控制包大小
提升體驗最直接的方法是控制小程式包的大小,這是最顯而易見的
-
勾選開發者工具中“上傳程式碼時,壓縮程式碼”選項;
-
及時清理無用的程式碼和資原始檔(包括無用的日誌程式碼)
-
減少資源包中的圖片等資源的數量和大小(理論上除了小icon,其他圖片資源從網路下載),圖片資源壓縮率有限
從開發者的角度看,控制程式碼包大小有助於減少小程式的啟動時間。對低於1MB的程式碼包,其下載時間可以控制在929ms(iOS)、1500ms(Android)內。
4. 採用分包載入機制
根據業務場景,將使用者訪問率高的頁面放在主包裡,將訪問率低的頁面放入子包裡,按需載入;
使用分包時需要注意程式碼和資原始檔目錄的劃分。啟動時需要訪問的頁面及其依賴的資原始檔應放在主包中。5 採用分包預載入技術
在4的基礎上,當使用者點選到子包的目錄時,還是有一個程式碼包下載的過程,這會感覺到明顯的卡頓,所以子包也不建議拆的太大,當然我們可以採用子包預載入技術,並不需要等到使用者點選到子包頁面後在下載子包,而是可以根據後期資料,做子包預載入,將使用者在當先頁可能點選的子包頁面先載入,當使用者點選後直接跳轉;
這種基於配置的子包預載入技術,是可以根據使用者網路型別來判斷的,當使用者處於網路條件好時才預載入;是靈活可控的6. 採用獨立分包技術
目前很多小程式主包+子包
(2M+6M)的方式,但是在做很多運營活動時,我們會發現活動(紅包)是在子包裡,但是運營、產品投放的落地頁連結是子包連結,這是的使用者在直達落地時,必須先下載主包內容(一般比較大),在下載子包內容(相對主包,較小),這使得在使用者停留時間比較短的小程式場景中,使用者體驗不是很好,而且浪費了很大部分流量;
7. 首屏載入的優化建議
7.1 提前請求
非同步請求可以在頁面onLoad就載入,不需要等頁面ready後在非同步請求資料;當然,如果能在前置頁面點選跳轉時預請求當前頁的核心非同步請求,效果會更好;
7.2 利用快取
利用storage API, 對變動頻率比較低的非同步資料進行快取,二次啟動時,先利用快取資料進行初始化渲染,然後後臺進行非同步資料的更新,這不僅優化了效能,在無網環境下,使用者也能很順暢的使用到關鍵服務;
7.3 避免白屏
可以在前置頁面將一些有用的欄位帶到當前頁,進行首次渲染(列表頁的某些資料--> 詳情頁),沒有資料的模組可以進行骨架屏的佔位,使使用者不會等待的很焦慮,甚至走了;
7.4 及時反饋
及時的對需要使用者等待的互動操作進行反饋,避免使用者以為小程式卡了,無響應
渲染效能優化
1. 小程式渲染原理
雙執行緒下的介面渲染,小程式的邏輯層和渲染層是分開的兩個執行緒。在渲染層,宿主環境會把WXML轉化成對應的JS物件,在邏輯層發生資料變更的時候,我們需要通過宿主環境提供的setData方法把資料從邏輯層傳遞到渲染層,再經過對比前後差異,把差異應用在原來的Dom樹上,渲染出正確的UI介面。
分析這個流程不難得知:頁面初始化的時間大致由頁面初始資料通訊時間和初始渲染時間兩部分構成。其中,資料通訊的時間指資料從邏輯層開始組織資料到檢視層完全接收完畢的時間,資料量小於64KB時總時長可以控制在30ms內。傳輸時間與資料量大體上呈現正相關關係,傳輸過大的資料將使這一時間顯著增加。因而減少傳輸資料量是降低資料傳輸時間的有效方式。2. 避免使用不當setData
在資料傳輸時,邏輯層會執行一次JSON.stringify
來去除掉setData
資料中不可傳輸的部分,之後將資料傳送給檢視層。同時,邏輯層還會將setData
所設定的資料欄位與data
合併,使開發者可以用this.data
讀取到變更後的資料。因此,為了提升資料更新的效能,開發者在執行setData
呼叫時,最好遵循以下原則:
2.1 不要過於頻繁呼叫setData,應考慮將多次setData合併成一次setData呼叫;
2.2 資料通訊的效能與資料量正相關,因而如果有一些資料欄位不在介面中展示且資料結構比較複雜或包含長字串,則不應使用setData
來設定這些資料;
2.3 與介面渲染無關的資料最好不要設定在data中,可以考慮設定在page物件的其他欄位下
提升資料更新效能方式的程式碼示例
Page({
onShow: function() {
// 不要頻繁呼叫setData
this.setData({ a: 1 })
this.setData({ b: 2 })
// 絕大多數時候可優化為
this.setData({ a: 1, b: 2 })
// 不要設定不在介面渲染時使用的資料,並將介面無關的資料放在data外
this.setData({
myData: {
a: '這個字串在WXML中用到了',
b: '這個字串未在WXML中用到,而且它很長…………………………'
}
})
// 可以優化為
this.setData({
'myData.a': '這個字串在WXML中用到了'
})
this._myData = {
b: '這個字串未在WXML中用到,而且它很長…………………………'
}
}
})
複製程式碼
- 利用setData進行列表區域性重新整理
在一個列表中,有n
條資料,採用上拉載入更多的方式,假如這個時候想對其中某一個資料進行點贊操作,還能及時看到點讚的效果
- 解決方法
1、可以採用setData全域性重新整理,點贊完成之後,重新獲取資料,再次進行全域性重新渲染,這樣做的優點是:方便,快捷!缺點是:使用者體驗極其不好,當使用者刷量100多條資料後,重新渲染量大會出現空白期(沒有渲染過來)
2、說到重點了,就是利用
setData
區域性重新整理
> a.將點讚的`id`傳過去,知道點的是那一條資料, 將點讚的`id`傳過去,知道點的是那一條資料
複製程式碼
<view wx:if="{{!item.status}}" class="btn" data-id="{{index}}" bindtap="couponTap">立即領取</view>
複製程式碼
> b.重新獲取資料,查詢相對應id的那條資料的下標(`index`是不會改變的)
> c.用setData進行區域性重新整理
複製程式碼
this.setData({
list[index] = newList[index]
})
複製程式碼
其實這個小操作對剛剛接觸到微信小程式的人來說應該是不容易發現的,不理解setData還有這樣的寫法。
2.4 切勿在後臺頁面進行setData
在一些頁面會進行一些操作,而到頁面跳轉後,程式碼邏輯還在執行,此時多個webview
是共享一個js程式;後臺的setData
操作會搶佔前臺頁面的渲染資源;
3. 使用者事件使用不當
檢視層將事件反饋給邏輯層時,同樣需要一個通訊過程,通訊的方向是從檢視層到邏輯層。因為這個通訊過程是非同步的,會產生一定的延遲,延遲時間同樣與傳輸的資料量正相關,資料量小於64KB時在30ms內。降低延遲時間的方法主要有兩個。
1.去掉不必要的事件繫結(WXML中的bind
和catch
),從而減少通訊的資料量和次數;
2.事件繫結時需要傳輸target
和currentTarget
的dataset
,因而不要在節點的data
字首屬性中放置過大的資料。
4. 檢視層渲染原理
4.1首次渲染
初始渲染髮生在頁面剛剛建立時。初始渲染時,將初始資料套用在對應的WXML片段上生成節點樹。節點樹也就是在開發者工具WXML皮膚中看到的頁面樹結構,它包含頁面內所有元件節點的名稱、屬性值和事件回撥函式等資訊。最後根據節點樹包含的各個節點,在介面上依次建立出各個元件。
在這整個流程中,時間開銷大體上與節點樹中節點的總量成正比例關係。因而減少WXML中節點的數量可以有效降低初始渲染和重渲染的時間開銷,提升渲染效能。
簡化WXML程式碼的例子
<view data-my-data="{{myData}}"> <!-- 這個 view 和下一行的 view 可以合併 -->
<view class="my-class" data-my-data="{{myData}}" bindtap="onTap">
<text> <!-- 這個 text 通常是沒必要的 -->
{{myText}}
</text>
</view>
</view>
<!-- 可以簡化為 -->
<view class="my-class" data-my-data="{{myData}}" bindtap="onTap">
{{myText}}
</view>
複製程式碼
4.2 重渲染
初始渲染完畢後,檢視層可以多次應用setData
的資料。每次應用setData
資料時,都會執行重渲染來更新介面。初始渲染中得到的data和當前節點樹會保留下來用於重渲染。每次重渲染時,將data
和setData
資料套用在WXML片段上,得到一個新節點樹。然後將新節點樹與當前節點樹進行比較,這樣可以得到哪些節點的哪些屬性需要更新、哪些節點需要新增或移除。最後,將setData
資料合併到data
中,並用新節點樹替換舊節點樹,用於下一次重渲染。
在進行當前節點樹與新節點樹的比較時,會著重比較setData
資料影響到的節點屬性。因而,去掉不必要設定的資料、減少setData
的資料量也有助於提升這一個步驟的效能。
5. 使用自定義元件
自定義元件的更新只在元件內部進行,不受頁面其他不能分內容的影響;比如一些運營活動的定時模組可以單獨抽出來,做成一個定時元件,定時元件的更新並不會影響頁面上其他元素的更新;各個元件也將具有各自獨立的邏輯空間。每個元件都分別擁有自己的獨立的資料、setData呼叫。
6. 避免不當的使用onPageScroll
每一次事件監聽都是一次檢視到邏輯的通訊過程,所以只在必要的時候監聽pageSrcoll
總結
小程式啟動載入效能
-
控制程式碼包的大小
-
分包載入
-
首屏體驗(預請求,利用快取,避免白屏,及時反饋
小程式渲染效能
-
避免不當的使用setData
-
合理利用事件通訊
-
避免不當的使用onPageScroll
-
優化檢視節點
-
使用自定義元件