微信小程序渲染性能調(diào)優(yōu)小結(jié)
網(wǎng)頁(yè)的性能優(yōu)化是前端開發(fā)老生常談的熱門話題,其中微信小程序因其頁(yè)面雙線程架構(gòu)設(shè)計(jì),所以性能優(yōu)化的手段跟傳統(tǒng)的 H5
應(yīng)用不太一樣。今天主要介紹一下微信小程序頁(yè)面雙線程架構(gòu)的特性給頁(yè)面渲染帶來(lái)的一些影響,以及應(yīng)對(duì)的一些渲染性能調(diào)優(yōu)策略。為了敘述方便,下文會(huì)把微信小程序簡(jiǎn)稱為小程序。
小程序的雙線程架構(gòu)
與傳統(tǒng)的瀏覽器Web頁(yè)面最大區(qū)別在于,小程序的是基于 雙線程
模型的,在這種架構(gòu)中,小程序的渲染層使用 WebView
作為渲染載體,而邏輯層則由獨(dú)立的 JsCore
線程運(yùn)行 JS
腳本,雙方并不具備數(shù)據(jù)直接共享的通道,因此渲染層和邏輯層的通信要由 Native
的 JSBrigde
做中轉(zhuǎn)。
小程序更新視圖數(shù)據(jù)的通信流程
每當(dāng)小程序視圖數(shù)據(jù)需要更新時(shí),邏輯層會(huì)調(diào)用小程序宿主環(huán)境提供的 setData
方法將數(shù)據(jù)從邏輯層傳遞到視圖層,經(jīng)過(guò)一系列渲染步驟之后完成UI視圖更新。完整的通信流程如下:
- 小程序邏輯層調(diào)用宿主環(huán)境的 setData 方法。
- 邏輯層執(zhí)行 JSON.stringify 將待傳輸數(shù)據(jù)轉(zhuǎn)換成字符串并拼接到特定的JS腳本,并通過(guò)evaluateJavascript 執(zhí)行腳本將數(shù)據(jù)傳輸?shù)戒秩緦印?/li>
- 渲染層接收到后, WebView JS 線程會(huì)對(duì)腳本進(jìn)行編譯,得到待更新數(shù)據(jù)后進(jìn)入渲染隊(duì)列等待 WebView 線程空閑時(shí)進(jìn)行頁(yè)面渲染。
- WebView 線程開始執(zhí)行渲染時(shí),待更新數(shù)據(jù)會(huì)合并到視圖層保留的原始 data 數(shù)據(jù),并將新數(shù)據(jù)套用在WXML片段中得到新的虛擬節(jié)點(diǎn)樹。經(jīng)過(guò)新虛擬節(jié)點(diǎn)樹與當(dāng)前節(jié)點(diǎn)樹的 diff 對(duì)比,將差異部分更新到UI視圖。同時(shí),將新的節(jié)點(diǎn)樹替換舊節(jié)點(diǎn)樹,用于下一次重渲染。
引發(fā)渲染性能問(wèn)題的一些原因
在上述通信流程中,一些不恰當(dāng)?shù)牟僮骺赡軙?huì)影響到頁(yè)面渲染的性能:
setData傳遞大量的新數(shù)據(jù)
數(shù)據(jù)的傳輸會(huì)經(jīng)歷跨線程傳輸和腳本編譯的過(guò)程,當(dāng)數(shù)據(jù)量過(guò)大,會(huì)增加腳本編譯的執(zhí)行時(shí)間,占用 WebView JS
線程。
下圖是我們做的一組測(cè)試統(tǒng)計(jì):在相同網(wǎng)絡(luò)環(huán)境下,各個(gè)機(jī)型分別對(duì)大小為 1KB
、 2KB
、 3KB
的數(shù)據(jù)執(zhí)行 setData
操作所消耗的時(shí)間。
從圖中可以看出, setData
數(shù)據(jù)傳輸量越大,數(shù)據(jù)傳輸所消耗的時(shí)間越大。
頻繁的執(zhí)行setData操作
頻繁的執(zhí)行 setData
會(huì)讓 WebView JS
線程一直忙碌于腳本的編譯、節(jié)點(diǎn)樹的對(duì)比計(jì)算和頁(yè)面渲染。導(dǎo)致的結(jié)果是:
- 頁(yè)面渲染結(jié)果有一定的延時(shí)。
- 用戶觸發(fā)頁(yè)面事件時(shí),因 WebView JS 線程忙碌,用戶事件未能及時(shí)的傳輸?shù)竭壿媽佣鴮?dǎo)致反饋延遲。
過(guò)多的頁(yè)面節(jié)點(diǎn)數(shù)
- 頁(yè)面初始渲染時(shí),渲染樹的構(gòu)建、計(jì)算節(jié)點(diǎn)幾何信息以及繪制節(jié)點(diǎn)到屏幕的時(shí)間開銷都跟頁(yè)面節(jié)點(diǎn)數(shù)量成正相關(guān)關(guān)系,頁(yè)面節(jié)點(diǎn)數(shù)量越多,渲染耗時(shí)越長(zhǎng)。
- 每次執(zhí)行
setData
更新視圖,WebView JS
線程都要遍歷節(jié)點(diǎn)樹計(jì)算新舊節(jié)點(diǎn)數(shù)差異部分。當(dāng)頁(yè)面節(jié)點(diǎn)數(shù)量越多,計(jì)算的時(shí)間開銷越大,減少節(jié)點(diǎn)樹節(jié)點(diǎn)數(shù)量可以有效降低重渲染的時(shí)間開銷。
渲染性能優(yōu)化
基于引發(fā)渲染性能問(wèn)題的原因,我們可以制定一些優(yōu)化策略來(lái)避免性能問(wèn)題的發(fā)生。
setData優(yōu)化
setData
作為邏輯層與視圖層通信的媒介,是最容易造成渲染性能瓶頸的 API
。我們?cè)谑褂?setData
時(shí)應(yīng)該遵循一些規(guī)則來(lái)盡可能避免性能問(wèn)題的發(fā)生:
減少 setData
數(shù)據(jù)傳輸量
- 僅傳輸視圖層使用到的數(shù)據(jù),其他
JS
環(huán)境用到的數(shù)據(jù)存放到data
對(duì)象外。 - 合理利用局部更新。
setData
是支持使用數(shù)據(jù)路徑
的方式對(duì)對(duì)象的局部字段進(jìn)行更新,我們可能會(huì)遇到這樣的場(chǎng)景:list
列表是從后臺(tái)獲取的數(shù)據(jù),并展示在頁(yè)面上,當(dāng)list
列表的第一項(xiàng)數(shù)據(jù)的src
字段需要更新時(shí),一般情況下我們會(huì)從后臺(tái)獲取新的list
列表,執(zhí)行setData
更新整個(gè)list
列表。
// 后臺(tái)獲取列表數(shù)據(jù) const list = requestSync(); // 更新整個(gè)列表 this.setData({ list });
實(shí)際上,只有個(gè)別字段需要更新時(shí),我們可以這么寫來(lái)避免整個(gè) list
列表更新:
// 后臺(tái)獲取列表數(shù)據(jù) const list = requestSync(); // 局部更新列表 this.setData({ 'list[0].src': list[0].src });
降低 setData
執(zhí)行頻率
- 在不影響業(yè)務(wù)流程的前提下,將多個(gè)
setData
調(diào)用合并執(zhí)行,減少線程間通信頻次。 - 當(dāng)需要在頻繁觸發(fā)的用戶事件(如
PageScroll
、Resize
事件)中調(diào)用setData
,合理的利用函數(shù)防抖(debounce)
和函數(shù)節(jié)流(throttle)
可以減少setData
執(zhí)行次數(shù)。
函數(shù)防抖(debounce)
:函數(shù)在觸發(fā)n秒后才執(zhí)行一次,如果在n秒內(nèi)重復(fù)觸發(fā)函數(shù),則重新計(jì)算時(shí)間。
函數(shù)節(jié)流(throttle)
:?jiǎn)挝粫r(shí)間內(nèi),只會(huì)觸發(fā)一次函數(shù),如果同一個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只會(huì)有一次生效。
除了讓開發(fā)者自覺遵循規(guī)則來(lái)減少 setData
數(shù)據(jù)傳輸量和執(zhí)行頻率之外,我們還可以自己設(shè)計(jì)一個(gè) diff
算法,重新對(duì) setData
進(jìn)行封裝,使得在 setData
執(zhí)行之前,讓待更新的數(shù)據(jù)與原 data
數(shù)據(jù)做 diff
對(duì)比,計(jì)算出數(shù)據(jù)差異 patch
對(duì)象,判斷 patch
對(duì)象是否為空,如果為空則跳過(guò)執(zhí)行更新,否則再將 patch
對(duì)象執(zhí)行 setData
操作,從而達(dá)到減少數(shù)據(jù)傳輸量和降低執(zhí)行 setData
頻率的目的。
// setData重新封裝成新的方法,使得數(shù)據(jù)更新前先對(duì)新舊數(shù)據(jù)做diff對(duì)比,再執(zhí)行setData方法 this.update = (data) => { return new Promise((resolve, reject) => { const result = diff(data, this.data); if (!Object.keys(result).length) { resolve(null); return; } this.setData(result, () => { resolve(result); }); }); }
當(dāng)然,可以直接引用這里 的現(xiàn)成 高性能小程序 setData
diff算法
具體流程如下圖:
善用自定義組件
小程序自定義組件的實(shí)現(xiàn)是由小程序官方設(shè)計(jì)的 Exparser
框架所支持,框架實(shí)現(xiàn)的自定義組件的組件模型與 Web Components
標(biāo)準(zhǔn)的 Shadow DOM
相似:
在頁(yè)面引用自定義組件后,當(dāng)初始化頁(yè)面時(shí), Exparser
會(huì)在創(chuàng)建頁(yè)面實(shí)例的同時(shí),也會(huì)根據(jù)自定義組件的注冊(cè)信息進(jìn)行組件實(shí)例化,然后根據(jù)組件自帶的 data
數(shù)據(jù)和組件WXML,構(gòu)造出獨(dú)立的 Shadow Tree
,并追加到頁(yè)面 Composed Tree
。創(chuàng)建出來(lái)的 Shadow Tree
擁有著自己獨(dú)立的邏輯空間、數(shù)據(jù)、樣式環(huán)境及setData調(diào)用:
基于自定義組件的 Shadow DOM
模型設(shè)計(jì),我們可以將頁(yè)面中一些需要高頻執(zhí)行 setData
更新的功能模塊(如倒計(jì)時(shí)、進(jìn)度條等)封裝成自定義組件嵌入到頁(yè)面中。當(dāng)這些自定義組件視圖需要更新時(shí),執(zhí)行的是組件自己的 setData
,新舊節(jié)點(diǎn)樹的對(duì)比計(jì)算和渲染樹的更新都只限于組件內(nèi)有限的節(jié)點(diǎn)數(shù)量,有效降低渲染時(shí)間開銷。
下圖是我們?cè)谖⒈P〕绦騑eDrive首頁(yè)中,將倒計(jì)時(shí)模塊抽取自定義組件前后的setData更新耗時(shí)對(duì)比:
從圖中可以看出,使用自定義組件后,倒計(jì)時(shí)模塊 setData
平均渲染耗時(shí)有了非常明顯的下降,實(shí)際在低端安卓機(jī)中體驗(yàn)會(huì)感覺明顯的更流暢。
當(dāng)然,并不是使用自定義組件越多會(huì)越好,頁(yè)面每新增一個(gè)自定義組件, Exparser
需要多管理一個(gè)組件實(shí)例,內(nèi)存消耗會(huì)更大,當(dāng)內(nèi)存占用上升到一定程度,有可能導(dǎo)致 iOS
將部分 WKWebView
回收,安卓機(jī)體驗(yàn)會(huì)變得更加卡頓。因此要合理的使用自定義組件,同時(shí)頁(yè)面設(shè)計(jì)也要注意不濫用標(biāo)簽。
總結(jié)
小程序雙線程架構(gòu)決定了數(shù)據(jù)通信優(yōu)化會(huì)是性能優(yōu)化中一個(gè)比較重要的點(diǎn)。而上述提到的幾個(gè)優(yōu)化建議只是優(yōu)化渲染性能中的一部分,要想讓你的頁(yè)面體驗(yàn)變得更加絲滑,就要熟悉小程序框架的底層原理,根據(jù)小程序框架的特點(diǎn),編寫出“合身”的前端代碼。
參考資料
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Javascript中函數(shù)名.length屬性用法分析(對(duì)比arguments.length)
這篇文章主要介紹了Javascript中函數(shù)名.length屬性用法,結(jié)合實(shí)例形式簡(jiǎn)單對(duì)比分析了與arguments.length屬性的用法區(qū)別,需要的朋友可以參考下2016-09-09自用js開發(fā)框架小成 學(xué)習(xí)js的朋友可以看看
前段時(shí)間項(xiàng)目需要用到j(luò)s樹,找了好多都不符合項(xiàng)目需求,后來(lái)發(fā)現(xiàn)了梅花雪樹和js框架,類似C#名稱空間的用法讓我眼前一亮,遂拿來(lái)主義,讀了幾遍代碼后就開工了(我是個(gè)急性子呵呵),完成了大部分,最近才找出來(lái)測(cè)試了下。2010-11-11真正好用的js驗(yàn)證上傳文件大小的簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇真正好用的js驗(yàn)證上傳文件大小的簡(jiǎn)單方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10原生js實(shí)現(xiàn)查詢天氣小應(yīng)用
這篇文章主要為大家詳細(xì)介紹了原生js實(shí)現(xiàn)查詢天氣的小應(yīng)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12