微信小程序性能優(yōu)化實(shí)現(xiàn)流程
為什么要做性能優(yōu)化?
一切性能優(yōu)化都是為了體驗(yàn)優(yōu)化
1. 使用小程序時(shí),是否會(huì)經(jīng)常遇到如下問(wèn)題?
-
打開是一直白屏
-
打開是loading態(tài),轉(zhuǎn)好幾圈
-
我的頁(yè)面點(diǎn)了怎么跳轉(zhuǎn)這么慢?
-
我的列表怎么越滑越卡?
2. 我們優(yōu)化的方向有哪些?
-
啟動(dòng)加載性能
-
渲染性能
3. 啟動(dòng)加載性能
1. 首次加載
你是否見過(guò)小程序首次加載時(shí)是這樣的圖?
這張圖中的三種狀態(tài)對(duì)應(yīng)的都是什么呢?
小程序啟動(dòng)時(shí),微信會(huì)為小程序展示一個(gè)固定的啟動(dòng)界面,界面內(nèi)包含小程序的圖標(biāo)、名稱和加載提示圖標(biāo)。此時(shí),微信會(huì)在背后完成幾項(xiàng)工作:下載小程序代碼包、加載小程序代碼包、初始化小程序首頁(yè)。下載到的小程序代碼包不是小程序的源代碼,而是編譯、壓縮、打包之后的代碼包。
2. 加載順序
小程序加載的順序是如何?
微信會(huì)在小程序啟動(dòng)前為小程序準(zhǔn)備好通用的運(yùn)行環(huán)境。這個(gè)運(yùn)行環(huán)境包括幾個(gè)供小程序使用的線程,并在其中完成小程序基礎(chǔ)庫(kù)的初始化,預(yù)先執(zhí)行通用邏輯,盡可能做好小程序的啟動(dòng)準(zhǔn)備。這樣可以顯著減少小程序的啟動(dòng)時(shí)間。
通過(guò)2,我們知道了,問(wèn)題1中第一張圖是資源準(zhǔn)備(代碼包下載);第二張圖是業(yè)務(wù)代碼的注入以及落地頁(yè)首次渲染;第三張圖是落地頁(yè)數(shù)據(jù)請(qǐng)求時(shí)的loading態(tài)(部分小程序存在)
3. 控制包大小
提升體驗(yàn)最直接的方法是控制小程序包的大小,這是最顯而易見的
-
勾選開發(fā)者工具中“上傳代碼時(shí),壓縮代碼”選項(xiàng);
-
及時(shí)清理無(wú)用的代碼和資源文件(包括無(wú)用的日志代碼)
-
減少資源包中的圖片等資源的數(shù)量和大?。ɡ碚撋铣诵con,其他圖片資源從網(wǎng)絡(luò)下載),圖片資源壓縮率有限
從開發(fā)者的角度看,控制代碼包大小有助于減少小程序的啟動(dòng)時(shí)間。對(duì)低于1MB的代碼包,其下載時(shí)間可以控制在929ms(iOS)、1500ms(Android)內(nèi)。
4. 采用分包加載機(jī)制
根據(jù)業(yè)務(wù)場(chǎng)景,將用戶訪問(wèn)率高的頁(yè)面放在主包里,將訪問(wèn)率低的頁(yè)面放入子包里,按需加載;
使用分包時(shí)需要注意代碼和資源文件目錄的劃分。啟動(dòng)時(shí)需要訪問(wèn)的頁(yè)面及其依賴的資源文件應(yīng)放在主包中。
5 采用分包預(yù)加載技術(shù)
在4的基礎(chǔ)上,當(dāng)用戶點(diǎn)擊到子包的目錄時(shí),還是有一個(gè)代碼包下載的過(guò)程,這會(huì)感覺(jué)到明顯的卡頓,所以子包也不建議拆的太大,當(dāng)然我們可以采用子包預(yù)加載技術(shù),并不需要等到用戶點(diǎn)擊到子包頁(yè)面后在下載子包,而是可以根據(jù)后期數(shù)據(jù),做子包預(yù)加載,將用戶在當(dāng)先頁(yè)可能點(diǎn)擊的子包頁(yè)面先加載,當(dāng)用戶點(diǎn)擊后直接跳轉(zhuǎn);
這種基于配置的子包預(yù)加載技術(shù),是可以根據(jù)用戶網(wǎng)絡(luò)類型來(lái)判斷的,當(dāng)用戶處于網(wǎng)絡(luò)條件好時(shí)才預(yù)加載;是靈活可控的
6. 采用獨(dú)立分包技術(shù)
目前很多小程序主包+子包(2M+6M)的方式,但是在做很多運(yùn)營(yíng)活動(dòng)時(shí),我們會(huì)發(fā)現(xiàn)活動(dòng)(紅包)是在子包里,但是運(yùn)營(yíng)、產(chǎn)品投放的落地頁(yè)鏈接是子包鏈接,這是的用戶在直達(dá)落地時(shí),必須先下載主包內(nèi)容(一般比較大),在下載子包內(nèi)容(相對(duì)主包,較小),這使得在用戶停留時(shí)間比較短的小程序場(chǎng)景中,用戶體驗(yàn)不是很好,而且浪費(fèi)了很大部分流量;
可以采用獨(dú)立分包技術(shù),區(qū)別于子包,和主包之間是無(wú)關(guān)的,在功能比較獨(dú)立的子包里,使用戶只需下載分包資源;
7. 首屏加載的優(yōu)化建議
7.1 提前請(qǐng)求
異步請(qǐng)求可以在頁(yè)面onLoad就加載,不需要等頁(yè)面ready后在異步請(qǐng)求數(shù)據(jù);當(dāng)然,如果能在前置頁(yè)面點(diǎn)擊跳轉(zhuǎn)時(shí)預(yù)請(qǐng)求當(dāng)前頁(yè)的核心異步請(qǐng)求,效果會(huì)更好;
7.2 利用緩存
利用storage API, 對(duì)變動(dòng)頻率比較低的異步數(shù)據(jù)進(jìn)行緩存,二次啟動(dòng)時(shí),先利用緩存數(shù)據(jù)進(jìn)行初始化渲染,然后后臺(tái)進(jìn)行異步數(shù)據(jù)的更新,這不僅優(yōu)化了性能,在無(wú)網(wǎng)環(huán)境下,用戶也能很順暢的使用到關(guān)鍵服務(wù);
7.3 避免白屏
可以在前置頁(yè)面將一些有用的字段帶到當(dāng)前頁(yè),進(jìn)行首次渲染(列表頁(yè)的某些數(shù)據(jù)--> 詳情頁(yè)),沒(méi)有數(shù)據(jù)的模塊可以進(jìn)行骨架屏的占位,使用戶不會(huì)等待的很焦慮,甚至走了;
7.4 及時(shí)反饋
及時(shí)的對(duì)需要用戶等待的交互操作進(jìn)行反饋,避免用戶以為小程序卡了,無(wú)響應(yīng)
渲染性能優(yōu)化
1. 小程序渲染原理
雙線程下的界面渲染,小程序的邏輯層和渲染層是分開的兩個(gè)線程。在渲染層,宿主環(huán)境會(huì)把WXML轉(zhuǎn)化成對(duì)應(yīng)的JS對(duì)象,在邏輯層發(fā)生數(shù)據(jù)變更的時(shí)候,我們需要通過(guò)宿主環(huán)境提供的setData方法把數(shù)據(jù)從邏輯層傳遞到渲染層,再經(jīng)過(guò)對(duì)比前后差異,把差異應(yīng)用在原來(lái)的Dom樹上,渲染出正確的UI界面。
分析這個(gè)流程不難得知:頁(yè)面初始化的時(shí)間大致由頁(yè)面初始數(shù)據(jù)通信時(shí)間和初始渲染時(shí)間兩部分構(gòu)成。其中,數(shù)據(jù)通信的時(shí)間指數(shù)據(jù)從邏輯層開始組織數(shù)據(jù)到視圖層完全接收完畢的時(shí)間,數(shù)據(jù)量小于64KB時(shí)總時(shí)長(zhǎng)可以控制在30ms內(nèi)。傳輸時(shí)間與數(shù)據(jù)量大體上呈現(xiàn)正相關(guān)關(guān)系,傳輸過(guò)大的數(shù)據(jù)將使這一時(shí)間顯著增加。因而減少傳輸數(shù)據(jù)量是降低數(shù)據(jù)傳輸時(shí)間的有效方式。
2. 避免使用不當(dāng)setData
在數(shù)據(jù)傳輸時(shí),邏輯層會(huì)執(zhí)行一次JSON.stringify來(lái)去除掉setData數(shù)據(jù)中不可傳輸?shù)牟糠?,之后將?shù)據(jù)發(fā)送給視圖層。同時(shí),邏輯層還會(huì)將setData所設(shè)置的數(shù)據(jù)字段與data合并,使開發(fā)者可以用this.data讀取到變更后的數(shù)據(jù)。因此,為了提升數(shù)據(jù)更新的性能,開發(fā)者在執(zhí)行setData調(diào)用時(shí),最好遵循以下原則:
2.1 不要過(guò)于頻繁調(diào)用setData,應(yīng)考慮將多次setData合并成一次setData調(diào)用;
2.2 數(shù)據(jù)通信的性能與數(shù)據(jù)量正相關(guān),因而如果有一些數(shù)據(jù)字段不在界面中展示且數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜或包含長(zhǎng)字符串,則不應(yīng)使用setData來(lái)設(shè)置這些數(shù)據(jù);
2.3 與界面渲染無(wú)關(guān)的數(shù)據(jù)最好不要設(shè)置在data中,可以考慮設(shè)置在page對(duì)象的其他字段下
提升數(shù)據(jù)更新性能方式的代碼示例
Page({ onShow: function() { // 不要頻繁調(diào)用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 絕大多數(shù)時(shí)候可優(yōu)化為 this.setData({ a: 1, b: 2 }) // 不要設(shè)置不在界面渲染時(shí)使用的數(shù)據(jù),并將界面無(wú)關(guān)的數(shù)據(jù)放在data外 this.setData({ myData: { a: '這個(gè)字符串在WXML中用到了', b: '這個(gè)字符串未在WXML中用到,而且它很長(zhǎng)…………………………' } }) // 可以優(yōu)化為 this.setData({ 'myData.a': '這個(gè)字符串在WXML中用到了' }) this._myData = { b: '這個(gè)字符串未在WXML中用到,而且它很長(zhǎng)…………………………' } } }) 復(fù)制代碼
2.4 切勿在后臺(tái)頁(yè)面進(jìn)行setData
在一些頁(yè)面會(huì)進(jìn)行一些操作,而到頁(yè)面跳轉(zhuǎn)后,代碼邏輯還在執(zhí)行,此時(shí)多個(gè)webview是共享一個(gè)js進(jìn)程;后臺(tái)的setData操作會(huì)搶占前臺(tái)頁(yè)面的渲染資源;
3. 用戶事件使用不當(dāng)
視圖層將事件反饋給邏輯層時(shí),同樣需要一個(gè)通信過(guò)程,通信的方向是從視圖層到邏輯層。因?yàn)檫@個(gè)通信過(guò)程是異步的,會(huì)產(chǎn)生一定的延遲,延遲時(shí)間同樣與傳輸?shù)臄?shù)據(jù)量正相關(guān),數(shù)據(jù)量小于64KB時(shí)在30ms內(nèi)。降低延遲時(shí)間的方法主要有兩個(gè)。
1.去掉不必要的事件綁定(WXML中的bind和catch),從而減少通信的數(shù)據(jù)量和次數(shù); 2.事件綁定時(shí)需要傳輸target和currentTarget的dataset,因而不要在節(jié)點(diǎn)的data前綴屬性中放置過(guò)大的數(shù)據(jù)。
4. 視圖層渲染原理
4.1首次渲染
初始渲染發(fā)生在頁(yè)面剛剛創(chuàng)建時(shí)。初始渲染時(shí),將初始數(shù)據(jù)套用在對(duì)應(yīng)的WXML片段上生成節(jié)點(diǎn)樹。節(jié)點(diǎn)樹也就是在開發(fā)者工具WXML面板中看到的頁(yè)面樹結(jié)構(gòu),它包含頁(yè)面內(nèi)所有組件節(jié)點(diǎn)的名稱、屬性值和事件回調(diào)函數(shù)等信息。最后根據(jù)節(jié)點(diǎn)樹包含的各個(gè)節(jié)點(diǎn),在界面上依次創(chuàng)建出各個(gè)組件。
在這整個(gè)流程中,時(shí)間開銷大體上與節(jié)點(diǎn)樹中節(jié)點(diǎn)的總量成正比例關(guān)系。因而減少WXML中節(jié)點(diǎn)的數(shù)量可以有效降低初始渲染和重渲染的時(shí)間開銷,提升渲染性能。
簡(jiǎn)化WXML代碼的例子
<view data-my-data="{{myData}}"> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> <text> {{myText}} text> view> view> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> {{myText}} view> 復(fù)制代碼
4.2 重渲染
初始渲染完畢后,視圖層可以多次應(yīng)用setData的數(shù)據(jù)。每次應(yīng)用setData數(shù)據(jù)時(shí),都會(huì)執(zhí)行重渲染來(lái)更新界面。初始渲染中得到的data和當(dāng)前節(jié)點(diǎn)樹會(huì)保留下來(lái)用于重渲染。每次重渲染時(shí),將data和setData數(shù)據(jù)套用在WXML片段上,得到一個(gè)新節(jié)點(diǎn)樹。然后將新節(jié)點(diǎn)樹與當(dāng)前節(jié)點(diǎn)樹進(jìn)行比較,這樣可以得到哪些節(jié)點(diǎn)的哪些屬性需要更新、哪些節(jié)點(diǎn)需要添加或移除。最后,將setData數(shù)據(jù)合并到data中,并用新節(jié)點(diǎn)樹替換舊節(jié)點(diǎn)樹,用于下一次重渲染。
在進(jìn)行當(dāng)前節(jié)點(diǎn)樹與新節(jié)點(diǎn)樹的比較時(shí),會(huì)著重比較setData數(shù)據(jù)影響到的節(jié)點(diǎn)屬性。因而,去掉不必要設(shè)置的數(shù)據(jù)、減少setData的數(shù)據(jù)量也有助于提升這一個(gè)步驟的性能。
5. 使用自定義組件
自定義組件的更新只在組件內(nèi)部進(jìn)行,不受頁(yè)面其他不能分內(nèi)容的影響;比如一些運(yùn)營(yíng)活動(dòng)的定時(shí)模塊可以單獨(dú)抽出來(lái),做成一個(gè)定時(shí)組件,定時(shí)組件的更新并不會(huì)影響頁(yè)面上其他元素的更新;各個(gè)組件也將具有各自獨(dú)立的邏輯空間。每個(gè)組件都分別擁有自己的獨(dú)立的數(shù)據(jù)、setData調(diào)用。
6. 避免不當(dāng)?shù)氖褂胦nPageScroll
每一次事件監(jiān)聽都是一次視圖到邏輯的通信過(guò)程,所以只在必要的時(shí)候監(jiān)聽pageSrcoll
總結(jié)
小程序啟動(dòng)加載性能
-
控制代碼包的大小
-
分包加載
-
首屏體驗(yàn)(預(yù)請(qǐng)求,利用緩存,避免白屏,及時(shí)反饋
小程序渲染性能
-
避免不當(dāng)?shù)氖褂胹etData
-
合理利用事件通信
-
避免不當(dāng)?shù)氖褂胦nPageScroll
-
優(yōu)化視圖節(jié)點(diǎn)
-
使用自定義組件