騰訊課堂小程序性能極致優(yōu)化——綜合篇
導(dǎo)語(yǔ) | 如果你的小程序也遇到了性能問(wèn)題,我們的實(shí)踐經(jīng)驗(yàn)也許可以給到你啟發(fā),我們從小程序的啟動(dòng)、加載到交互都進(jìn)行了探索。順便說(shuō)一句,這篇文章在騰訊內(nèi)部曾被小程序技術(shù)總監(jiān)打賞。
1. 緣起
事情,要從一個(gè)周末愜意的下午開(kāi)始說(shuō)起……
那天,手機(jī)突然被喚醒,彈出多條微信消息。原來(lái)是這周末正在校園推廣的活動(dòng)群發(fā)來(lái)的,想起之前大家有條不紊的開(kāi)發(fā)進(jìn)度,和產(chǎn)品溝通的友好過(guò)程,應(yīng)該是活動(dòng)反響不錯(cuò)。
現(xiàn)實(shí)是殘酷的:
“我們的小程序打開(kāi)慢成狗!”
“這個(gè) loading 加載的過(guò)程也太久了!”
“滾動(dòng)加載有點(diǎn)卡,而且很容易報(bào)錯(cuò)……”
看到的是最直接的控訴。
看到用戶的錄屏,這幾個(gè)問(wèn)題確實(shí)是有出現(xiàn),所以我們還是需要對(duì)小程序進(jìn)行一次主流程的性能優(yōu)化,三句控訴可以總結(jié)為3個(gè)點(diǎn):
小程序啟動(dòng)慢
小程序請(qǐng)求慢
小程序交互慢
2.定位
2.1. 啟動(dòng)慢
收到反饋后第一反應(yīng)是,用戶是不是網(wǎng)速太慢了,自己跑一遍,發(fā)現(xiàn)自己的手機(jī)跑起來(lái)都是沒(méi)問(wèn)題的,灰常流暢,下意識(shí)的可能想錄個(gè)屏回復(fù)過(guò)去。

不過(guò)有用戶錄屏在那,當(dāng)然不能這么草率,所以我們查了下管理后臺(tái)小程序在不同網(wǎng)絡(luò)下的大盤(pán)數(shù)據(jù):
| 網(wǎng)絡(luò) | 啟動(dòng)耗時(shí) |
| 總體 | 3.6s |
| WIFI | 3.5s |
| 4G | 3.9s |
| 2G/3G | 4.1s |
從統(tǒng)計(jì)看,總體3.7s的啟動(dòng)耗時(shí),網(wǎng)絡(luò)對(duì)于啟動(dòng)耗時(shí)是會(huì)有影響的,但影響沒(méi)有很大,就算是2G-3G下跟大盤(pán)的數(shù)據(jù)對(duì)比也沒(méi)有慢很多,可見(jiàn)事情并不簡(jiǎn)單。
于是我們從另外一個(gè)維度來(lái)看一下大盤(pán)數(shù)據(jù):
| 機(jī)型 | 啟動(dòng)耗時(shí) | JS注入 | 初次渲染 |
| 總體 | 3.6s | 0.29s | 0.16s |
| 高端機(jī) | 2.9s | 0.19s | 0.06s |
| 中端機(jī) | 4.8s | 0.42s | 0.19s |
| 低端機(jī) | 7.9s | 0.72s | 0.43s |
從這里就可以看出問(wèn)題來(lái)了,手機(jī)的性能對(duì)于小程序的啟動(dòng)速度影響非常大,低端機(jī)相對(duì)高端機(jī)有2-3倍的差距,特別是渲染層甚至有5-6倍的差距,而且問(wèn)題反饋的用戶所使用的手機(jī)也確實(shí)是中低端機(jī),但用戶使用什么手機(jī)我們也沒(méi)法控制,那這里有辦法去優(yōu)化嗎?
針對(duì)這個(gè)問(wèn)題,我們需要了解一下小程序的啟動(dòng)過(guò)程,根據(jù)官方文檔的介紹,小程序的啟動(dòng)可以分為下面幾個(gè)步驟:

上圖描述了用戶點(diǎn)擊小程序開(kāi)始到頁(yè)面開(kāi)始請(qǐng)求數(shù)據(jù)的一個(gè)完整的冷啟動(dòng)過(guò)程,而小程序初始化的過(guò)程(信息準(zhǔn)備、環(huán)境準(zhǔn)備)占用了比較長(zhǎng)的時(shí)間,但這部分的工作是由微信客戶端來(lái)完成,開(kāi)發(fā)者無(wú)法干預(yù),所以我們只能聚焦于后續(xù)的步驟(下載代碼包、注入代碼包、初次渲染)。
根據(jù)官方文檔的介紹,這一部分的可優(yōu)化手段有:
減小代碼包體積
降低代碼復(fù)雜度
減少同步代碼接口調(diào)用
降低頁(yè)面結(jié)構(gòu)復(fù)雜度
減少自定義組件數(shù)量
后面4條在技術(shù)上沒(méi)有特別好的限制手段,需要我們?cè)?Code Review 的時(shí)候?qū)?fù)雜度和開(kāi)銷(xiāo)大的接口調(diào)用進(jìn)行把關(guān),復(fù)雜度這里還可以借助如 CodeCC(騰訊內(nèi)部代碼檢查工具) 這類(lèi)工具去進(jìn)行分析,減少自定義組件數(shù)量,這個(gè)是比較難以抉擇的,需要在代碼可讀性、可復(fù)用性之間做個(gè)取舍,不是本次優(yōu)化的重點(diǎn)。
所以我們就重點(diǎn)關(guān)注的代碼包體積問(wèn)題,通過(guò)我們的 CI 記錄可以收集到我們的總包大?。?/span>

可以看到主包體積達(dá)到1949.71KB,接近了2M的極限了,在通過(guò)依賴分析后,發(fā)現(xiàn)除了一些是未使用到的模塊和組件外,很大一部分內(nèi)容是靜態(tài)資源,同時(shí)我們也在官方文檔中看到這樣一句話:
小程序代碼包在下載時(shí)會(huì)使用 ZSTD 算法進(jìn)行壓縮,這些資源文件會(huì)占用大量代碼包體積,并且通常難以進(jìn)一步被壓縮,對(duì)于下載耗時(shí)的影響比代碼文件大得多。
所以我們要減小代碼包的體積,最直接的方法就是將非必要的資源去除掉:
對(duì)靜態(tài)資源進(jìn)行優(yōu)化,將非必要的靜態(tài)資源文件上傳到CDN
對(duì)小程序的組件進(jìn)行依賴分析,過(guò)濾掉未使用的組件
同時(shí)我們還關(guān)注到,有一些分包特別小,但是由于是普通分包,在打開(kāi)這些頁(yè)面的時(shí)候還需要先下載主包,這里在包下載耗時(shí)上其實(shí)是有一些浪費(fèi)的,比較典型的就是 WebView 頁(yè)面,他們往往只需要對(duì)參數(shù)進(jìn)行處理,對(duì)于主包的依賴不是很強(qiáng),所以這里還有一個(gè)可以優(yōu)化的點(diǎn):
對(duì)獨(dú)立性比較強(qiáng)的頁(yè)面進(jìn)行獨(dú)立分包,盡可能的減少包下載耗時(shí)
2.2. 請(qǐng)求慢
我們通過(guò)日志查到這個(gè)用戶的首頁(yè)數(shù)據(jù)請(qǐng)求返回會(huì)到3-4s,請(qǐng)求慢在正常情況下會(huì)有這么兩種情況:
并發(fā)量突增導(dǎo)致服務(wù)器響應(yīng)慢
用戶網(wǎng)速較慢導(dǎo)致發(fā)送請(qǐng)求和接收請(qǐng)求變慢
我們通過(guò)日志統(tǒng)計(jì)發(fā)現(xiàn)用戶的訪問(wèn)時(shí)間端,請(qǐng)求量跟平時(shí)保持一致,看大盤(pán)請(qǐng)求耗時(shí)的統(tǒng)計(jì),也沒(méi)有產(chǎn)生大的波動(dòng):

所以基本可以排除是后臺(tái)的問(wèn)題,雖然大盤(pán)的數(shù)據(jù)在500ms左右,但是當(dāng)用戶網(wǎng)絡(luò)不好的情況下,這一塊要怎么保證呢?
答案當(dāng)然是做提前拉取,當(dāng)用戶冷啟動(dòng)的時(shí)候,我們可以使用小程序官方提供的數(shù)據(jù)預(yù)拉取能力提前拉取,從小程序的啟動(dòng)耗時(shí)看,完全可以 cover 掉我們的接口請(qǐng)求耗時(shí),可以讓小程序啟動(dòng)成功后就直接渲染頁(yè)面。
在熱啟動(dòng)的情況下,請(qǐng)求慢主要體現(xiàn)在用戶交互時(shí)發(fā)生的請(qǐng)求和頁(yè)面切換時(shí)發(fā)生的請(qǐng)求,交互的情況我們下一節(jié)在分析,這里主要看頁(yè)面切換,從我們的統(tǒng)計(jì)數(shù)據(jù)來(lái)看,頁(yè)面切換的耗時(shí)大概在400ms左右,而其中能夠利用的時(shí)間大概是50ms-100ms:

利用頁(yè)面切換的這個(gè)時(shí)間提前對(duì)頁(yè)面的數(shù)據(jù)進(jìn)行加載,可以減少用戶感觀上的數(shù)據(jù)請(qǐng)求時(shí)間,同時(shí)在第一次請(qǐng)求之后可以根據(jù)一定的策略對(duì)頁(yè)面的數(shù)據(jù)進(jìn)行緩存,從而可以達(dá)到二次進(jìn)入頁(yè)面秒開(kāi)的效果。
總結(jié)來(lái)看,請(qǐng)求慢的優(yōu)化手段有下面幾個(gè),而且理論上效果都會(huì)很顯著:
冷啟動(dòng)開(kāi)啟數(shù)據(jù)預(yù)拉取
頁(yè)面路由切換時(shí)提前拉取數(shù)據(jù)
對(duì)數(shù)據(jù)進(jìn)行緩存
2.3. 交互慢
先說(shuō)一下這里的交互慢具體指什么,我們收到用戶反饋的現(xiàn)象是:用戶首屏順利加載出來(lái)之后,后續(xù)滾動(dòng)加載和一些按鈕點(diǎn)擊的響應(yīng)非常慢并且很容易報(bào)錯(cuò)。收到這個(gè)反饋后定位了很久,講道理如果是因?yàn)橛脩艟W(wǎng)絡(luò)問(wèn)題導(dǎo)致的請(qǐng)求很慢,應(yīng)該所有的請(qǐng)求都會(huì)很慢,但是用戶表現(xiàn)出來(lái)的現(xiàn)象是后續(xù)的加載以及交互很慢,反而首屏還算正常。
通過(guò)日志查詢,我們發(fā)現(xiàn)這個(gè)用戶的請(qǐng)求報(bào)錯(cuò)都是請(qǐng)求超時(shí),為什么超時(shí)會(huì)集中在交互加載這里呢?定位了一段時(shí)間后我們發(fā)現(xiàn)一個(gè)用戶的報(bào)錯(cuò)都集中在首屏加載之后就立馬下滑或者點(diǎn)擊,如果過(guò)了一段時(shí)間再點(diǎn)擊又不會(huì)報(bào)錯(cuò)。
發(fā)現(xiàn)這個(gè)現(xiàn)象后,我們想到了官方文檔關(guān)于網(wǎng)絡(luò)使用說(shuō)明的一個(gè)限制:
wx.request、wx.uploadFile、wx.downloadFile的最大并發(fā)限制是 10 個(gè)
再結(jié)合我們對(duì)于 wx.request 的封裝,請(qǐng)求超時(shí)的計(jì)時(shí)器是從調(diào)用 wx.request 的時(shí)候就開(kāi)始了,如果請(qǐng)求并發(fā)超過(guò)了限制,那么就很容易出現(xiàn)請(qǐng)求超時(shí),而當(dāng)我們從第一個(gè)業(yè)務(wù)接口請(qǐng)求到數(shù)據(jù)后就會(huì)進(jìn)行一系列的數(shù)據(jù)上報(bào),包括 pv、組件曝光、關(guān)鍵鏈路打點(diǎn)等等,所以我們利用 Whistle 的 resDelay 方法,將我們的上報(bào)請(qǐng)求都延遲5000ms返回,果然就復(fù)現(xiàn)了用戶反饋的那種情況。

找到問(wèn)題之后也就明確了需要優(yōu)化的方向:
保障與用戶體驗(yàn)相關(guān)的業(yè)務(wù)請(qǐng)求正常發(fā)送
交互慢還有別的原因嗎?在繼續(xù)挖掘性能瓶頸的過(guò)程中,發(fā)現(xiàn)騰訊課堂小程序的課程詳情頁(yè)內(nèi)容非常多,有5-6屏的高度,而用戶只會(huì)關(guān)心首屏是不是更快的呈現(xiàn)出來(lái),但是我們?cè)镜奶幚矸绞绞潜容^粗暴的,拿到詳情頁(yè)的數(shù)據(jù)之后對(duì)數(shù)據(jù)進(jìn)行處理,格式化成整個(gè)頁(yè)面所需要的數(shù)據(jù)之后一次性調(diào)用 this.setData 來(lái)更新頁(yè)面,所以如果要提升首屏速度,這里需要做的就是:
頁(yè)面分步渲染
2.4. 優(yōu)化點(diǎn)歸總
再歸總一下需要優(yōu)化的點(diǎn)和方向:
啟動(dòng)慢主要從優(yōu)化代碼包上下手:
對(duì)靜態(tài)資源進(jìn)行優(yōu)化,將非必要的靜態(tài)資源文件上傳到CDN
對(duì)小程序的組件進(jìn)行依賴分析,過(guò)濾掉未使用的組件
對(duì)獨(dú)立性比較強(qiáng)的頁(yè)面進(jìn)行獨(dú)立分包,減少主包下載耗時(shí)
請(qǐng)求慢主要從預(yù)加載和緩存下手:
冷啟動(dòng)開(kāi)啟數(shù)據(jù)預(yù)拉取
頁(yè)面路由切換時(shí)提前拉取數(shù)據(jù)
對(duì)數(shù)據(jù)進(jìn)行緩存
交互慢需要從發(fā)起請(qǐng)求和頁(yè)面渲染下手:
保障與用戶體驗(yàn)相關(guān)的業(yè)務(wù)請(qǐng)求正常發(fā)送
頁(yè)面分步渲染
3. 優(yōu)化
3.1. 啟動(dòng)優(yōu)化
3.1.1. 獨(dú)立分包
由于用戶反饋主要是因?yàn)樾@推廣活動(dòng)來(lái)的,而活動(dòng)頁(yè)面我們是通過(guò) WebView 內(nèi)嵌 H5 來(lái)承載的,而 WebView 頁(yè)面的啟動(dòng)過(guò)程和小程序原生頁(yè)面還不太一樣:

實(shí)際上 WebView 頁(yè)面只需要完善登錄態(tài)傳遞的功能,對(duì)于主包的依賴不是很大,而且這部分頁(yè)面更大的性能問(wèn)題需要在 h5 那邊來(lái)優(yōu)化,所以我們第一時(shí)間對(duì)其進(jìn)行了獨(dú)立分包處理。
最終的優(yōu)化效果還不錯(cuò),因?yàn)閱?dòng)過(guò)程中不需要下載主包,啟動(dòng)性能提升了30%。
3.1.2. 靜態(tài)資源上CDN
我們小程序構(gòu)成主要是由原生頁(yè)面 + kbone 頁(yè)面組成的,kbone 是采用的官方的方案,通過(guò) webpack 構(gòu)建,有很多單獨(dú)打包靜態(tài)資源的方案。而我們的原生頁(yè)面是使用 gulp 進(jìn)行構(gòu)建的,原來(lái)主要的功能是將源碼中的 ts 轉(zhuǎn)成 js,同時(shí)對(duì) css 文件通過(guò) postcss 轉(zhuǎn)成 wxss,由于 wxss 不支持引用相對(duì)路徑,所以在 wxss 中引用的圖片和字體都是轉(zhuǎn)成 base64 的,然后對(duì)其余的文件如 json、wxml 文件則直接復(fù)制到產(chǎn)物中去。
這樣的處理方式比較粗暴,通過(guò) postcss 將 background-image 所引用的本地圖片都轉(zhuǎn)成 base64,還會(huì)導(dǎo)致很多圖片在項(xiàng)目中占用了2倍的體積。

所以我們首先需要將源碼下的靜態(tài)資源匹配到并單獨(dú)構(gòu)建出來(lái),并且為了規(guī)避同名文件的問(wèn)題,需要對(duì)資源打個(gè) hashtag,我們這里需要用到一個(gè) gulp 插件gulp-rev,這個(gè)插件可以對(duì)基于資源的內(nèi)容進(jìn)行 hash。

將圖片上到 CDN 后,把 css、js、json、wxml 中的引用路徑替換成 CDN 地址,具體的替換邏輯如下。

3.1.3. 過(guò)濾未使用組件
隨著業(yè)務(wù)的迭代,不可避免的會(huì)有一些組件被廢棄了但是難以察覺(jué),通過(guò)我們團(tuán)隊(duì)開(kāi)發(fā)的小程序腳手架 imweb-miniprogram-cli 對(duì)頁(yè)面所使用到的組件進(jìn)行分析,可以把項(xiàng)目中未使用到的組件給過(guò)濾出來(lái),不會(huì)打包到最終的產(chǎn)物中,大致的思路如下:

從app.json開(kāi)始,拿到小程序所配置的所有頁(yè)面和分包,通過(guò)檢查 App、頁(yè)面、分包中所使用的自定義組件來(lái)進(jìn)行收集,并且遞歸檢查自定義組件所使用的組件,如果檢測(cè)到有未使用的組件,也會(huì)給到提示,非常友好:

可以看到在我們的項(xiàng)目中就發(fā)現(xiàn)了好幾個(gè)未使用到的組件。
3.2. 請(qǐng)求優(yōu)化
3.2.1. 數(shù)據(jù)預(yù)拉取
數(shù)據(jù)預(yù)拉取需要在小程序的管理后臺(tái)開(kāi)啟,數(shù)據(jù)來(lái)源可以選擇開(kāi)發(fā)者服務(wù)器或者云開(kāi)發(fā),選擇開(kāi)發(fā)者服務(wù)器的話會(huì)有一些限制,如果是直接填寫(xiě) CGI 地址,就只能拉取一種數(shù)據(jù),不夠靈活,而如果再搭建一個(gè)服務(wù)去做預(yù)拉取涉及到的工作量又會(huì)很大,所以我們選擇的是云開(kāi)發(fā)的方式,大致流程如下圖:

當(dāng)小程序啟動(dòng)的時(shí)候,微信客戶端會(huì)根據(jù)配置去拉取指定的云函數(shù),在云函數(shù)中通過(guò) cl5 調(diào)用業(yè)務(wù)后臺(tái)的服務(wù)拉取到需要的數(shù)據(jù),拉取到后客戶端會(huì)將數(shù)據(jù)緩存在本地,當(dāng)小程序啟動(dòng)成功后,在業(yè)務(wù)代碼中調(diào)用wx.getBackgroundFetchData就可以拿到預(yù)拉取的數(shù)據(jù),如果緩存數(shù)據(jù)拉到的是所需要的數(shù)據(jù)則可以直接渲染,如果不是則降級(jí)到業(yè)務(wù)中再拉一次接口。
在云函數(shù)中可以拿到本次小程序啟動(dòng)的path和query參數(shù),所以我們可以根據(jù)這兩個(gè)參數(shù)來(lái)判斷本次預(yù)拉取需要調(diào)用業(yè)務(wù)后臺(tái)的哪個(gè)服務(wù),從而達(dá)到從不同的頁(yè)面啟動(dòng)小程序都可以通過(guò)一個(gè)云函數(shù)預(yù)拉取到所需要的數(shù)據(jù)。
const preFetchMap = {
'pages/index/index': fetchIndex,
'pages/course/course': fetchCourse,
}
// 云函數(shù)入口函數(shù)
exports.main = async (event) => {
const { path, query = '' } = event;
const fetchFn = preFetchMap[path];
if (fetchFn) {
const res = await fetchFn(query);
return res;
}
return {
error: {
event,
retcode: -1002,
msg: `${path}頁(yè)面未設(shè)置預(yù)拉取邏輯`
}
};
};
不過(guò)要注意的是,因?yàn)樾〕绦蜃陨碜隽撕芏喑跏蓟膬?yōu)化,有可能在小程序啟動(dòng)后,預(yù)拉取的數(shù)據(jù)還沒(méi)有返回,所以我們做了進(jìn)一步的優(yōu)化,在業(yè)務(wù)拉取的過(guò)程中通過(guò) wx.onBackgroundFetchData監(jiān)聽(tīng)預(yù)拉取的返回,收到返回就直接渲染 ,盡可能的使用預(yù)拉取的數(shù)據(jù)來(lái)渲染首屏。

3.2.2. 提前拉取 & 數(shù)據(jù)緩存
前面已經(jīng)提到過(guò),提前拉取就是要利用小程序切換頁(yè)面的空隙開(kāi)始拉取數(shù)據(jù),從而在感官上較少數(shù)據(jù)請(qǐng)求的時(shí)間,整體的邏輯是通過(guò)封裝的跳轉(zhuǎn)邏輯,對(duì)應(yīng)的頁(yè)面添加不同的數(shù)據(jù)拉取邏輯,并將拉取的 promise 掛載在 App 上,當(dāng)頁(yè)面切換完成后優(yōu)先使用 App 上的 promise 來(lái)獲取數(shù)據(jù)。
數(shù)據(jù)緩存則是在數(shù)據(jù)拉取成功后,將比較固定的數(shù)據(jù)通過(guò) wx.setStorage 緩存在本地,當(dāng)?shù)诙吻袚Q到這個(gè)頁(yè)面時(shí),先使用本地緩存的數(shù)據(jù)進(jìn)行渲染,后面再通過(guò)拉取的數(shù)據(jù)來(lái)進(jìn)行更新。

3.3. 交互優(yōu)化
3.3.1. 業(yè)務(wù)請(qǐng)求保障
保障業(yè)務(wù)請(qǐng)求的核心思路是讓業(yè)務(wù)請(qǐng)求優(yōu)先,我們封裝了一個(gè) 排隊(duì)請(qǐng)求模塊 ,通過(guò)對(duì) wx.request API的攔截,將請(qǐng)求根據(jù)配置有個(gè)優(yōu)先級(jí)排序,低優(yōu)先級(jí)的會(huì)在請(qǐng)求并發(fā)數(shù)達(dá)到一定的閾值之后被推到等待隊(duì)列 WaitingQueue 中,留出足夠的通道給到高優(yōu)先級(jí)的業(yè)務(wù)請(qǐng)求。

3.3.2. 分步渲染
這里的方案相信大家也能很好理解,主要是優(yōu)先處理首屏需要的數(shù)據(jù)并通過(guò) setData 更新視圖,然后在處理其余的數(shù)據(jù)。但根據(jù)官方文檔的說(shuō)明:
setData函數(shù)用于將數(shù)據(jù)從邏輯層發(fā)送到視圖層(異步),同時(shí)改變對(duì)應(yīng)的this.data的值(同步)。
而小程序代碼執(zhí)行順序也遵循JS的事件循環(huán)機(jī)制,僅僅是在處理后的數(shù)據(jù)調(diào)用 setData ,然后繼續(xù)或者通過(guò) Promise 處理下一步的話,并不能達(dá)到分步渲染的目的,而直接通過(guò)回調(diào)的方式在 setTimeout 中使用嵌套渲染,代碼的可讀性會(huì)變差,同時(shí)也不是很優(yōu)雅。我們的解決方式是利用 setTimeout 封裝了一個(gè)符合 Promise 標(biāo)準(zhǔn)的方法,從而可以像使用 Promise 那樣繼續(xù)分步渲染:

4. 成果
經(jīng)過(guò)這一系列的優(yōu)化,效果還是比較明顯的:
4.1. 包大小
在包大小方面:
總包從9132.94KB減小到6736.42KB,減少了27%;
主包從1949.71KB減小到985.96KB,減少了49.5%;
從啟動(dòng)耗時(shí)的數(shù)據(jù)看,下載耗時(shí)和JS注入耗時(shí)都有明顯的下降:

再看打開(kāi)耗時(shí)分布,可以看到3s內(nèi)打開(kāi)的用戶比例有明顯增加,從56.26%增加到64.25%;

4.2. 請(qǐng)求耗時(shí)
數(shù)據(jù)預(yù)拉取,提前拉取,數(shù)據(jù)緩存在冷啟動(dòng)和頁(yè)面切換時(shí)都起到了很不錯(cuò)的效果:
首頁(yè)請(qǐng)求速度從平均400ms下降到50ms,優(yōu)化了87.5%;
課詳頁(yè)的請(qǐng)求速度從平均800ms下降到90ms,優(yōu)化了88.75%;
而數(shù)據(jù)緩存讓二次訪問(wèn)時(shí)頁(yè)面可以秒開(kāi):

使用排隊(duì)請(qǐng)求之后,對(duì)網(wǎng)絡(luò)請(qǐng)求順序的干預(yù)效果還比較明顯,灰度用戶業(yè)務(wù)請(qǐng)求耗時(shí)平均有50-100ms,約15%的優(yōu)化;
同時(shí)我們通過(guò)分析耗時(shí)分別在80分位、50分位、20分位的效果發(fā)現(xiàn),請(qǐng)求耗時(shí)越長(zhǎng),優(yōu)化效果越明顯,也就是說(shuō)在弱網(wǎng)情況下能夠更好的發(fā)揮作用。

4.3. 渲染
使用分步渲染后,我們的頁(yè)面可以在處理完首屏的基礎(chǔ)數(shù)據(jù)之后就立即開(kāi)始渲染了,由于我們的目錄結(jié)構(gòu)比較復(fù)雜,處理起來(lái)耗時(shí)比較長(zhǎng),所以第二部才處理目錄,實(shí)際的渲染效果如下圖:

首屏可以比原來(lái)提前100ms-150ms渲染出來(lái)。
5. 總結(jié)
我們本次的性能優(yōu)化對(duì)小程序啟動(dòng)、請(qǐng)求、交互、渲染多個(gè)方面都進(jìn)行了性能的挖掘,是在對(duì)基礎(chǔ)庫(kù)版本要求不高的情況下能做到的極致了。
以我們的核心頁(yè)面首頁(yè)和課程詳情頁(yè)來(lái)說(shuō):
首頁(yè)冷啟動(dòng)耗時(shí)開(kāi)發(fā)者可干預(yù)的部分優(yōu)化大概是1300下載 + 300注入 + 170首渲 + 430請(qǐng)求 = 2200ms -> 750 + 245 + 170 + 50 = 1215ms,優(yōu)化了45%
課詳頁(yè)冷啟動(dòng)耗時(shí)開(kāi)發(fā)者可干預(yù)的部分優(yōu)化大概是1300下載 + 300注入 + 170首渲 + 790請(qǐng)求 = 2560ms -> 750 + 245 + 170 + 100 = 1265ms,優(yōu)化了50.5%
頁(yè)面切換首次進(jìn)入詳情頁(yè)耗時(shí)從400路由 + 800請(qǐng)求 + 450處理 = 1650ms -> 400 + 720 + 300 = 1420ms,優(yōu)化了14%
二次進(jìn)入詳情頁(yè)面幾乎看不到加載和渲染過(guò)程
還有更多的優(yōu)化手段嗎?官方還提供了一些比較高級(jí)的功能,對(duì)基礎(chǔ)庫(kù)版本要求比較高的,例如:
組件的按需注入和用時(shí)注入可以進(jìn)一步減小代碼包下載耗時(shí),但是在我們發(fā)布時(shí)這個(gè)功能還有點(diǎn)問(wèn)題,會(huì)導(dǎo)致首頁(yè)的自定義組件加載不出來(lái),所以暫時(shí)沒(méi)有使用到。
還可以使用2.11.1開(kāi)始支持的初始渲染緩存,不必等到邏輯層初始化完畢,可以更早的開(kāi)始渲染視圖。
尚在試驗(yàn)階段的分包異步化,利用異步加載模塊的方式也可以減少代碼包的下載耗時(shí)和JS的注入耗時(shí)。
利用這些能力可以在更多細(xì)節(jié)上進(jìn)行優(yōu)化,我們也將進(jìn)一步探索和跟進(jìn),如果你有更好的方案歡迎討論。
這篇文章是騰訊課堂小程序優(yōu)化的綜合介紹,后面我們還有【網(wǎng)絡(luò)請(qǐng)求優(yōu)化】和【獨(dú)立分包與性能測(cè)試】?jī)蓚€(gè)專(zhuān)項(xiàng)優(yōu)化,敬請(qǐng)期待。
內(nèi)推社群
我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。
