本(ben)文主要(yao)是根據微信小程(cheng)序官方優化建議和(he)《2018微信公(gong)開課第(di)七季上海(hai)站·小程(cheng)序專場(chang)》的性(xing)(xing)能(neng)(neng)優化方案(an),針對性(xing)(xing)我們的小程(cheng)序項(xiang)目進行性(xing)(xing)能(neng)(neng)優化實踐,將過程(cheng)記(ji)錄下(xia)來,方便以后查看,同時也希望能(neng)(neng)幫助(zhu)到其他小伙伴(ban),做(zuo)好性(xing)(xing)能(neng)(neng)優化。畢竟一(yi)切性(xing)(xing)能(neng)(neng)優化都(dou)是為了更好的用戶體(ti)驗(yan)。
這些(xie)問(wen)題(ti)的場景都反映了小程序的性能(neng)問(wen)題(ti),直接影(ying)響到(dao)用(yong)戶體(ti)驗。
官方(fang)建議(yi)從這兩(liang)方(fang)面進行(xing)優化:
小(xiao)程序在整個(ge)啟動流程中,一般需(xu)要完成幾項(xiang)工作:
開發(fa)者(zhe)可以在第2,3去優化小程序的啟動性(xing)能。
小(xiao)程序在首(shou)次打開時,會去下(xia)載并執行(xing)代碼(ma)包(bao),隨著代碼(ma)包(bao)大小(xiao)的上升(sheng),耗時也會相應增(zeng)加。可以(yi)(yi)采取以(yi)(yi)下(xia)方案:
對(dui)開(kai)發者而(er)言(yan),能使小程(cheng)序有(you)更(geng)大的代碼體積,承載更(geng)多的功(gong)能與(yu)服務;而(er)對(dui)用戶(hu)而(er)言(yan),可以更(geng)快(kuai)地(di)打開(kai)小程(cheng)序,同時(shi)在不影響(xiang)啟動(dong)速度(du)前提下使用更(geng)多功(gong)能。
建(jian)議開發者按照功能的劃(hua)分(fen)(fen),拆分(fen)(fen)成幾個分(fen)(fen)包,當需要用到(dao)某個功能時,才加載這(zhe)個功能對應的分(fen)(fen)包。
我(wo)們(men)的(de)一個(ge)小程序在兩年多前開(kai)始(shi)開(kai)發的(de),在設計之初,我(wo)們(men)沒有考慮到(dao)這(zhe)一點,當時也沒有小程序分包的(de)功(gong)能。好吧,我(wo)們(men)還是迎來了這(zhe)個(ge)問題。
微信(xin)小(xiao)程(cheng)序在開(kai)發文檔(dang)中明確指出(chu),小(xiao)程(cheng)序的所有(you)包大小(xiao)必須限制在2M以內(nei),超(chao)過大小(xiao),就算在開(kai)發者工具(ju)中都不能正常(chang)預覽,更(geng)不能上(shang)傳發版。解決(jue)問題的方(fang)法:
將靜態資源(yuan)圖(tu)片(pian)壓(ya)縮(suo),因為(wei)小程序(xu)的壓(ya)縮(suo)算法對(dui)圖(tu)片(pian)的壓(ya)縮(suo)微乎(hu)其微,于是互,筆(bi)者對(dui)圖(tu)片(pian)進(jin)行(xing)一輪壓(ya)縮(suo),并且將重復使用(yong)的圖(tu)片(pian),進(jin)行(xing)了(le)公共(gong)提取,雖然官方推(tui)薦(jian)使用(yong)網絡圖(tu)片(pian),但是還需要去維護靜態資源(yuan),嫌麻煩,就放(fang)棄(qi)。
將項目中的棄用的頁面,以及不用的三(san)方,進(jin)行了一(yi)波清除。
很多項目(mu)現在都是通(tong)過webpack打包(bao)成不通(tong)的分(fen)包(bao),資源懶加載的形式(shi)來優(you)化(hua),小程序也提供了這(zhe)個功能:分(fen)包(bao),筆者按(an)照按(an)照功能劃分(fen)的原則,將(jiang)同(tong)一(yi)個功能下的頁(ye)面和(he)邏輯放置于(yu)同(tong)一(yi)個目(mu)錄下,成為一(yi)個分(fen)包(bao)。

注意:1. 自定義第(di)三方組件,需(xu)要放在(zai)主包(bao)內,miniprogram_npm文件會直接(jie)打到主包(bao)里;2. 小程序的tab切換(huan)頁,必須(xu)放在(zai)主包(bao)里。
分(fen)(fen)包預下載(zai)是為了解決首(shou)次進入分(fen)(fen)包頁(ye)面(mian)時的延遲(chi)問題(ti)而設(she)計的。如果能夠在用戶進入分(fen)(fen)包頁(ye)面(mian)之前就(jiu)預先將分(fen)(fen)包下載(zai)完畢,那么(me)進入分(fen)(fen)包頁(ye)面(mian)的延遲(chi)就(jiu)能夠盡可(ke)能降低。
用戶進行了某個操作,再去下載分包,延遲操作用戶體驗很差,于是乎筆者對上面的分包設置分包預下載。在 app.json 文件中配置(zhi):
"preloadRule": {
"pages/work/index": {
"network": "all",
"packages": [
"package-work",
"package-field-statistics"
]
},
"pages/appeal/index": {
"network": "all",
"packages": [
"package-appeal"
]
}
},
復制代碼
這里建(jian)議不(bu)要一次性(xing)把所有分包預下載,這樣的操作同樣回帶來(lai)性(xing)能問題。
小(xiao)程(cheng)序中(zhong)的(de)某些場景(jing)(如廣(guang)告頁(ye)、活動頁(ye)、支付(fu)頁(ye)等),通(tong)常功能(neng)不是很(hen)復雜且(qie)相對(dui)獨(du)立(li),對(dui)啟動性能(neng)有很(hen)高(gao)的(de)要求。使用獨(du)立(li)分(fen)(fen)(fen)包,可以獨(du)立(li)于主包和其他分(fen)(fen)(fen)包運行。從獨(du)立(li)分(fen)(fen)(fen)包中(zhong)頁(ye)面進入(ru)小(xiao)程(cheng)序時,不需(xu)要下載主包。
建議開(kai)發者(zhe)將(jiang)部分(fen)對啟動性(xing)能要求很高的(de)頁面放到特殊的(de)獨立分(fen)包中。
項(xiang)目中沒有適合的場(chang)景,尚未實(shi)踐。
大部分小程序在渲染首頁時,需要依賴服務端的接口數據,接口請求放到頁面的生命周期 onLoad 中,而不是 onReady 里。 `:
監聽到頁(ye)面加載,就校驗登錄情況,請求頁(ye)面數據
onLoad: function (options) {
app.checkAuth((error, token) => {
if (error) {
return
}
// 請(qing)求該頁面(mian)的數據
})
},
復制代碼
小程序提供了wx.setStorageSync等(deng)異步讀寫本(ben)地緩存的能力,數據(ju)存儲在本(ben)地,返回的會比(bi)網絡請求快。
登錄成功后將用戶的token,以(yi)及用戶信息都(dou)可以(yi)緩存到本地,記得(de)退出登錄的時候清(qing)楚緩存,:joy:。
/**
* 設置本地 token 緩存
* @param {Object} session 服務器返回的數據
* @param {String} session.access_token 存取token
* @param {String} session.refresh_token 刷新token
* @param {String} session.expires_in 有效期限,以秒為單位
*/
export function set(session) {
const localSession = Object.assign({}, session, {
expires_timestamp: getExpireTimestamp(session.expires_in)
});
wx.setStorageSync(SESSION_KEY, localSession);
_token = session.access_token;
}
export function clear() {
wx.removeStorageSync(SESSION_KEY);
clearTimeout(refresh_timer);
_token = null;
}
復制代碼
推薦開發者延(yan)遲請(qing)求非(fei)關(guan)鍵渲染數據(ju),縮(suo)短(duan)網絡請(qing)求時(shi)延(yan),與(yu)視圖層渲染無(wu)關(guan)的數據(ju)盡量不要放在 data 中,以(yi)免(mian)傳輸(shu)垃圾數據(ju),加快(kuai)首屏(ping)渲染完(wan)成時(shi)間。
通過(guo)id請求詳(xiang)情的(de)情況(kuang),id在(zai)渲染層不需要,就(jiu)可以(yi)不把(ba)id,定義(yi)在(zai)data中:
// 原來(lai)代碼(ma)
data: {
id: ‘’,
// ….
},
onLoad: function (options) {
this.setData({
id: options.id
})
// ….
}
// 改寫后 不把id定義到(dao)data中
data: {
// ….
},
app.checkAuth((error, token) => {
const id = options.id === undefined ? '' : options.id;
this.id = id
})
復制代碼
接(jie)口返回(hui)的數(shu)(shu)(shu)據(ju)要(yao)做數(shu)(shu)(shu)據(ju)處理,不(bu)要(yao)直接(jie)都(dou)塞給(gei)data,減少(shao)冗余數(shu)(shu)(shu)據(ju)的雙線程回(hui)傳。也是(shi) 精簡(jian)首(shou)屏(ping)數(shu)(shu)(shu)據(ju)優化的一部分。
在(zai)小程(cheng)序啟動(dong)流(liu)程(cheng)中,會順序執(zhi)行app.onLaunch, app.onShow, page.onLoad, page.onShow, page.onReady,所以,盡量避免在(zai)這些生(sheng)命(ming)周期(qi)中使用Sync結(jie)尾的同步API,如 wx.setStorageSync,wx.getSystemInfoSync 等(deng)。
項目中沒有(you)這樣使用,有(you)先見(jian)之(zhi)明。:smile:
小(xiao)程(cheng)序的視(shi)(shi)圖(tu)層(ceng)目(mu)前(qian)使用(yong)(yong) WebView 作(zuo)為渲(xuan)染載體(ti),而邏輯(ji)(ji)層(ceng)是(shi)由獨(du)(du)立的 JavascriptCore 作(zuo)為運行環(huan)境(jing)。在架構上(shang),WebView 和 JavascriptCore 都是(shi)獨(du)(du)立的模塊,并不具備(bei)數據直接共享的通道。當前(qian),視(shi)(shi)圖(tu)層(ceng)和邏輯(ji)(ji)層(ceng)的數據傳(chuan)(chuan)輸,實(shi)際上(shang)通過(guo)兩邊提(ti)供的 evaluateJavascript 所實(shi)現。即用(yong)(yong)戶傳(chuan)(chuan)輸的數據,需要(yao)將其(qi)轉換(huan)(huan)為字符串形式傳(chuan)(chuan)遞,同時把(ba)轉換(huan)(huan)后(hou)的數據內(nei)容拼接成一(yi)份(fen) JS 腳(jiao)本,再通過(guo)執行 JS 腳(jiao)本的形式傳(chuan)(chuan)遞到兩邊獨(du)(du)立環(huan)境(jing)。
而 evaluateJavascript 的(de)執(zhi)行會受(shou)很多方面(mian)的(de)影響,數據到(dao)達視(shi)圖層并不是(shi)實時(shi)的(de)。
** 常見的 setData 操作(zuo)錯誤 **
導致了兩個后果(guo):
目前項目代碼還是(shi)比較規范的,我(wo)們并沒有把setData當成一個(ge)普通(tong)的對象(xiang)去調用(yong),曉得每(mei)次(ci)使用(yong)都需要(yao)兩(liang)個(ge)線程間通(tong)信,WebView再(zai)去渲染的。哇,好棒。
由setData的底層實現可知,我們的數據(ju)傳輸實際是一(yi)次(ci) evaluateJavascript 腳(jiao)本(ben)過(guo)程,當數據(ju)量(liang)過(guo)大(da)時(shi)會增加腳(jiao)本(ben)的編譯(yi)執(zhi)行時(shi)間(jian),占用 WebView JS 線程。
目前每(mei)個接口的(de)數(shu)據量并大,數(shu)據的(de)量級(ji)還沒達到(dao)影響腳步執行(xing)的(de)程度,有需要的(de)話(hua)再優化吧(ba)。
當頁(ye)面(mian)(mian)進入后臺態(tai)(用(yong)戶(hu)不可見),不應該繼續去(qu)進行setData,后臺態(tai)頁(ye)面(mian)(mian)的(de)渲染用(yong)戶(hu)是無法感受(shou)的(de),另外后臺態(tai)頁(ye)面(mian)(mian)去(qu)setData也會(hui)搶占前臺頁(ye)面(mian)(mian)的(de)執行。
A頁面上有個定時器,此時打開了B頁面,A頁面的定時器還在運行,繼續搶占B頁面的資源,B頁面卡頓了,但是并不是B頁面的造成的性能問題,這種問題就不太好排查。希望大家都能做個有始有終的人,定時器不用了要清除。下面demo,定時器在 onHide 時(shi)要清除掉。切記切記:point_down:
/**
* 生(sheng)命周期函數(shu)--監聽頁面顯示(shi)
*/
onShow: function () {
clearTimeout(getTodaytime)
this.updateNowTime()
},
/**
* 生命周期函(han)數(shu)--監聽頁面隱藏
*/
onHide: function () {
// 取(qu)消(xiao)定時器(qi) 防(fang)止小程(cheng)序內(nei)存不足,崩(beng)潰
clearTimeout(getTodaytime)
},
updateNowTime() {
getTodaytime = setInterval(() => {
const myDate = new Date();
const hours = myDate.getHours())
const minutes = myDate.getMinutes())
const seconds = myDate.getSeconds())
const newTime = hours + ':' + minutes + ':' + seconds;
this.setData({
newTime: newTime
})
}, 1000)
},
復制代碼
項目中展示沒(mei)用使(shi)用該事件。
在(zai)(zai)需要頻(pin)繁更(geng)新的場(chang)景下(xia),自定義組件的更(geng)新只在(zai)(zai)組件內部進行,不受頁(ye)面(mian)其他部分內容復(fu)雜性的影(ying)響。