小程序模板網(wǎng)

從小程序不支持DOM操作開始深入分析小程序運(yùn)行機(jī)制

發(fā)布時間:2020-05-13 09:49 所屬欄目:小程序開發(fā)教程

小程序現(xiàn)在如日中天,各大公司都推出了自己的小程序平臺,目前看來運(yùn)行機(jī)制都差不多,數(shù)據(jù)形成視圖,渲染和邏輯分成兩個線程,交互通過線程通信實(shí)現(xiàn)。

剛開始接觸小程序開發(fā)的時候,看到小程序的語法覺得很奇怪。看著像react和vue的結(jié)合體,疑惑為什么要這么費(fèi)力的實(shí)現(xiàn)這么一套機(jī)制。難道是為了體現(xiàn)技術(shù)nb? 用了一會就發(fā)現(xiàn)問題了,照搬pc開發(fā)的那套思想,特么小程序里不支持dom相關(guān)的api,很不方便。翻了很多遍微信和支付寶小程序的官方文檔,終于有了一點(diǎn)理解。

本文大部分是官方文檔引用加上自己一點(diǎn)總結(jié)。

web開發(fā)渲染線程和腳本線程是互斥的,這也是為什么長時間的腳本運(yùn)行可能會導(dǎo)致頁面失去響應(yīng)。開發(fā)者可以使用到各種瀏覽器暴露出來的 DOM API,進(jìn)行 DOM 選中和操作。 而在小程序中,二者是分開的,分別運(yùn)行在不同的線程中,邏輯層運(yùn)行在 JSCore 中,并沒有一個完整瀏覽器對象,因而缺少相關(guān)的DOM API和BOM API。

由于支付寶官方文檔說明過于簡略,本文結(jié)合了微信和支付寶小程序的特點(diǎn)總結(jié),結(jié)合如有差異,歡迎指正。

這部分在 微信 和支付寶小程序官方文檔都有說明

瀏覽器環(huán)境中渲染線程和js線程是互斥的

由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和GUI線程同時運(yùn)行),那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了。

小程序頁面文件結(jié)構(gòu)

小程序分為 app 和 page 兩層。app 用來描述整個應(yīng)用,page 用來描述各個頁面。

app 由三個文件組成,必須放在項(xiàng)目的根目錄。

文件 必需 作用
app.js 小程序邏輯
app.json 小程序全局設(shè)置
app.acss 小程序全局樣式表

page 由四個文件組成,分別是:

文件 必需 作用
app.js 頁面邏輯
app.axml 頁面結(jié)構(gòu)
app.json 頁面配置
app.acss 頁面樣式

為了方便開發(fā)者減少配置項(xiàng),描述頁面的四個文件必須具有相同的路徑與文件名。

單邏輯線程與多webview渲染線程

小程序的邏輯層和渲染層是分開的兩個線程,小程序的運(yùn)行環(huán)境分成渲染層和邏輯層,其中 WXML 模板和 WXSS 樣式工作在渲染層,JS腳本工作在邏輯層。在渲染層,宿主環(huán)境會把WXML轉(zhuǎn)化成對應(yīng)的JS對象,在邏輯層發(fā)生數(shù)據(jù)變更的時候,我們需要通過宿主環(huán)境提供的setData方法把數(shù)據(jù)從邏輯層傳遞到渲染層,再經(jīng)過對比前后差異,把差異應(yīng)用在原來的Dom樹上,渲染出正確的UI界面。

小程序的渲染層和邏輯層分別由2個線程管理:渲染層的界面使用了WebView 進(jìn)行渲染;邏輯層采用JsCore線程運(yùn)行JS腳本。一個小程序存在多個界面,所以渲染層存在多個WebView線程,這兩個線程的通信會經(jīng)由客戶端做中轉(zhuǎn),邏輯層發(fā)送網(wǎng)絡(luò)請求也經(jīng)由Native轉(zhuǎn)發(fā),小程序的通信模型下圖所示。

從邏輯組成來說,一個小程序是由多個“頁面”組成的“程序”。宿主環(huán)境提供了 App() 構(gòu)造器用來注冊一個程序App,需要留意的是App() 構(gòu)造器必須寫在項(xiàng)目根目錄的app.js里,App實(shí)例是單例對象,在其他JS腳本中可以使用宿主環(huán)境提供的 getApp() 來獲取程序?qū)嵗?/p>

小程序開發(fā)框架的邏輯層使用 JavaScript 引擎為小程序提供開發(fā)者 JavaScript 代碼的運(yùn)行環(huán)境以及微信小程序的特有功能。 邏輯層將數(shù)據(jù)進(jìn)行處理后發(fā)送給視圖層,同時接受視圖層的事件反饋。 開發(fā)者寫的所有代碼最終將會打包成一份 JavaScript 文件,并在小程序啟動的時候運(yùn)行,直到小程序銷毀。這一行為類似 ServiceWorker,所以邏輯層也稱之為 App Service。

const app = getApp(); 
復(fù)制代碼

參考資料: 微信1 、 微信2 、 微信3

小程序的運(yùn)行環(huán)境

js運(yùn)行引擎

小程序的 JavaScript 代碼分為邏輯層腳本和 sjs/wxs 腳本

支付寶文檔 說sjs和邏輯層運(yùn)行在相同的 JavaScript 引擎的不同線程中。

微信文檔 又表示wxs是運(yùn)行在webview中的,并且提供了更為強(qiáng)大的功能:如果在 iOS 設(shè)備上小程序內(nèi)的 WXS 會比 JavaScript 代碼快 2 ~ 20 倍,在 android 設(shè)備上二者運(yùn)行效率無差異;減少通信的次數(shù),讓事件在視圖層(Webview)響應(yīng);用來響應(yīng)小程序事件,目前只能響應(yīng)內(nèi)置組件的事件,不支持自定義組件事件;還能調(diào)用邏輯層的事件;

對兩者sjs/wxs差別表示吃瓜狀態(tài),支付寶的sjs明顯功能弱了很多,畢竟雞肋,一般用來模擬vue里的compued功能使用,無法作為事件回調(diào)。微信的wxs提供了更強(qiáng)大的支持。

試了下sjs不帶類似computed緩存功能

兩個平臺都會對新預(yù)發(fā)的代碼進(jìn)行 babel 轉(zhuǎn)換,使 JavaScript 引擎支持絕大多數(shù) ES6 的新特性,但是對于內(nèi)置對象未提供完全的Polyfill,具體支持情況可以查閱 支付寶 、 微信

  • 在 iOS 上,小程序邏輯層的 javascript 代碼運(yùn)行在 JavaScriptCore 中;
  • 在 Android 上,小程序邏輯層的 javascript 代碼運(yùn)行在 V8 中;

視圖層渲染

  • 在 iOS 上,視圖層是由 WKWebView 來渲染的
  • 在 Android 上,微信是由自研 XWeb 引擎基于 Mobile Chrome 內(nèi)核來渲染的,支付寶未找到說明,猜測是UC

小程序渲染native組件原理

小程序中,有一些組件其實(shí)是調(diào)用原生組件的,如map、video等,這些復(fù)雜交互的控件,原生能帶來更好的性能與原生體驗(yàn)。

簡單說就是在期望插入原生控件的位置渲染一個HTML元素,拿到此DOM的位置,客戶端在相同的位置上,根據(jù)寬高插入一塊原生區(qū)域,位置或?qū)捀甙l(fā)生變化時,組件會通知客戶端做相應(yīng)的調(diào)整。 可以直接看 官方文檔

為什么這么設(shè)計

微信小程序的官方介紹 很全面了

回到標(biāo)題的問題。因?yàn)檫壿媽覵ervice中的代碼與WebView中的代碼完全隔離,JavaScriptCore中并沒有document,window等對象(ECMAScript標(biāo)準(zhǔn)沒有規(guī)定DOM,這其實(shí)是瀏覽器提供的)。js和視圖(dom所在)沒有運(yùn)行在同一容器中。

小程序開發(fā)可做的優(yōu)化

減少包的大小

很好理解,小程序初次啟動時,客戶端需要從 CDN 下載小程序資源包,此后,如果小程序代碼包未更新且還被保留在緩存中,則下載小程序代碼包的步驟會被跳過??梢宰龅模?/p>

  • 減少在代碼包中直接嵌入的資源文件,建議從 CDN 渠道上傳
  • 清理無用代碼和結(jié)構(gòu)
  • 提升首屏,可以使用分包

減少setData次數(shù),合并setData

每一次setData都是線程通信

支付寶小程序提供了$batchedUpdates

this.$batchedUpdates(() => {
    this.setData({
      counter: this.data.counter + 1,
    });
    this.setData({
      counter: this.data.counter + 1,
    });
  });
復(fù)制代碼

優(yōu)化setData

setData是線程通信傳遞數(shù)據(jù),傳輸時數(shù)據(jù)需要序列化,框架提供了指定路徑設(shè)置數(shù)據(jù)的方便,避免一次傳輸完整數(shù)據(jù)。

this.setData({
  'array[0]': 1,
  'obj.x':2,
});
復(fù)制代碼

針對長列表, 支付寶小程序 提供了優(yōu)化方法$spliceData,使用方式對應(yīng)js數(shù)組的splice

this.$spliceData({ 'a.b': [1, 0, 5, 6] })
復(fù)制代碼

針對長列表做優(yōu)化,避免每次傳遞整個列表,只會從對應(yīng)組件節(jié)點(diǎn)開始做差異比較

使用 wxs /sjs

小程序中事件響應(yīng)也需要通過線程通信,如果頻繁的觸發(fā)可能會造成卡頓。例如頁面有 2 個元素 A 和 B,用戶在 A 上做 touchmove 手勢,要求 B 也跟隨移動。一次 touchmove 事件的響應(yīng)過程為:

a、touchmove 事件從視圖層(Webview)拋到邏輯層(App Service)

b、邏輯層(App Service)處理 touchmove 事件,再通過 setData 來改變 B 的位置

微信小程序里可以使用wxs響應(yīng)事件優(yōu)化,wxs是運(yùn)行在webview中的,不需要跨線程通信。WXS 函數(shù)的除了純邏輯的運(yùn)算,還可以通過封裝好的ComponentDescriptor 實(shí)例來訪問以及設(shè)置組件的 class 和樣式,對于交互動畫,設(shè)置 style 和 class 足夠了:

const wxsFunction = function(event, ownerInstance) {
    const instance = ownerInstance.selectComponent('.classSelector') // 返回組件的實(shí)例
    instance.setStyle({
        "font-size": "14px" // 支持rpx
    })
    instance.getDataset()
    instance.setClass(className)
    // ...
    return false // 不往上冒泡,相當(dāng)于調(diào)用了同時調(diào)用了stopPropagation和preventDefault
}
復(fù)制代碼

一次 touchmove 的響應(yīng)需要經(jīng)過 2 次的邏輯層和渲染層的通信以及一次渲染,通信的耗時比較大。此外 setData 渲染也會阻塞其它腳本執(zhí)行,導(dǎo)致了整個用戶交互的動畫過程會有延遲。 官方性能優(yōu)化文檔: 微信 、 支付寶 ;


易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開源 碼云倉庫:starfork
本文地址:http://22321a.com/wxmini/doc/course/25125.html 復(fù)制鏈接 如需定制請聯(lián)系易優(yōu)客服咨詢:800182392 點(diǎn)擊咨詢
QQ在線咨詢