淺析微信小程序的底層架構原理


一、小程序基礎知識

  小程序是基於WEB規范,采用HTML、CSS和JS等搭建的一套框架,微信官方給它們取的名字:WXML、WXSS,但本質上還是在整個WEB體系之下構建的。WXML說到底就是xml的一個子集。WXML采用微信自定義的少量標簽WXSS,大家可以理解為就是自定義的CSS。實現邏輯部分的JS還是通用的ES規范,並且runtime還是Webview(IOS WKWEBVIEW、ANDROID X5)

1、小程序的組成結構

  一個完整的小程序主要由以下幾部分組成:

  一個入口文件:app.js

  一個全局樣式:app.wxss

  一個全局配置:app.json

  頁面:pages下,每個頁面再按文件夾划分,每個頁面4個文件

(1)視圖層:wxml,wxss

(2)邏輯層:js,json(頁面配置,不是必須)

  注:pages里面還可以再根據模塊划分子目錄,孫子目錄,只需要在app.json里注冊時填寫路徑就行。

2、小程序項目打包:

  編輯器它本身也是基於WEB技術體系實現的,nwjs+react,nwjs簡單是說就是node+webkit,node提供給我們本地api能力,而webkit提供給我們web能力,兩者結合就能讓我們使用JS+HTML實現本地應用程序。既然有nodejs,那上面的打包選項里的功能就好實現了。

(1)ES6轉ES5:引入babel-core的node包

(2)CSS補全:引入postcss和autoprefixer的node包(postcss和autoprefixer的原理看這里)

(3)代碼壓縮:引入uglifyjs的node包

  打包后目錄結構:

  所有的小程序基本都最后都被打成上面的結構:

(1)WAService.js 框架JS庫,提供邏輯層基礎的API能力

(2)WAWebview.js 框架JS庫,提供視圖層基礎的API能力

(3)WAConsole.js 框架JS庫,控制台

(4)app-config.js 小程序完整的配置,包含我們通過app.json里的所有配置,綜合了默認配置型

(5)app-service.js 我們自己的JS代碼,全部打包到這個文件

(6)page-frame.html 小程序視圖的模板文件,所有的頁面都使用此加載渲染,且所有的WXML都拆解為JS實現打包到這里

(7)pages 所有的頁面,這個不是我們之前的wxml文件了,主要是處理WXSS轉換,使用js插入到header區域。

3、與H5頁面的區別

  小程序和普通的 h5 頁面到底有什么區別呢?

(1)運行環境:小程序基於瀏覽器內核重構的內置解析器,而 h5 的宿主環境是瀏覽器。所以小程序中沒有 DOM 和 BOM 的相關 API , jQuery 和一些 NPM 包都不能在小程序中使用;

  普通網頁開發可以使用各種瀏覽器提供的 DOM API,進行 DOM 操作,而小程序的邏輯層和渲染層是分開的,邏輯層運行在 JSCore 中,並沒有一個完整瀏覽器對象,因而缺少相關的DOM API和BOM API。

(2)系統權限:小程序能獲得更多的系統權限,如網絡通信狀態、數據緩存能力等;

(3)渲染機制:小程序的邏輯層和渲染層是分開的,而 h5 頁面 UI 渲染跟 JavaScript 的腳本執行都在一個單線程中,互斥。所以 h5 頁面中長時間的腳本運行可能會導致頁面失去響應。

  普通網頁開發渲染線程和腳本線程是互斥的,這也是為什么長時間的腳本運行可能會導致頁面失去響應,而在小程序中,二者是分開的,分別運行在不同的線程中。

  此外,小程序面對的是 iOS 和 Android 微信客戶端和輔助開發的小程序開發者工具。根據官方文檔,這三大運行環境也是有所區別的:

  所以微信小程序介於 web 端和原生 App 之間,能夠豐富調用功能接口,同時又跨平台。

二、小程序架構

1、雙線程模型

  微信小程序的框架包含兩部分:View視圖層、App Service邏輯層。View層用來渲染頁面結構,App Service層用來邏輯處理、數據請求、接口調用,它們在兩個進程(兩個Webview)里運行。

  視圖層和邏輯層通過系統層的JSBridage進行通信,邏輯層把數據變化通知到視圖層,觸發視圖層頁面更新,視圖層把觸發的事件通知到邏輯層進行業務處理。

  小程序的渲染層和邏輯層分別由2個線程管理:

(1)視圖層:界面渲染相關的任務全都在 WebView 線程里執行。一個小程序存在多個界面,所以渲染層存在多個 WebView 線程

(2)邏輯層:采用 JsCore 線程運行JS腳本。

  視圖層和邏輯層通過系統層的 WeixinJsBridage 進行通信:邏輯層把數據變化通知到視圖層,觸發視圖層頁面更新,視圖層把觸發的事件通知到邏輯層進行業務處理。

2、渲染流程

  把開發者的 JS 邏輯代碼放到單獨的線程去運行,但在 Webview 線程里,開發者就沒法直接操作 DOM。

  那要怎么去實現動態更改界面呢?

  如上圖所示,邏輯層和試圖層的通信會由 Native (微信客戶端)做中轉,邏輯層發送網絡請求也經由 Native 轉發。

  這也就是說,我們可以把 DOM 的更新通過簡單的數據通信來實現。

  Virtual DOM 相信大家都已有了解,大概是這么個過程:用 JS 對象模擬 DOM 樹 -> 比較兩棵虛擬 DOM 樹的差異 -> 把差異應用到真正的 DOM 樹上。

  頁面渲染的具體流程是:在渲染層,宿主環境會把 WXML 轉化成對應的 JS 對象,在邏輯層發生數據變更的時候,我們需要通過宿主環境提供的 setData 方法把數據從邏輯層傳遞到渲染層,再經過對比前后差異,把差異應用在原來的Dom樹上,渲染出正確的UI界面。

(1)在渲染層把 WXML 轉化成對應的 JS 對象。

(2)在邏輯層發生數據變更的時候,通過宿主環境提供的 setData 方法把數據從邏輯層傳遞到 Native,再轉發到渲染層。

(3)經過對比前后差異,把差異應用在原來的 DOM 樹上,更新界面。

  我們通過把 WXML 轉化為數據,通過 Native 進行轉發,來實現邏輯層和渲染層的交互和通信。

3、雙線程模型設計的好處

  雙線程模型是小程序框架與業界大多數前端 Web 框架不同之處。基於這個模型,可以更好地管控以及提供更安全的環境。缺點是帶來了無處不在的異步問題(任何數據傳遞都是線程間的通信,也就是都會有一定的延時),不過小程序在框架層面已經封裝好了異步帶來的時序問題。

  為什么要這樣設計呢,前面也提到了管控和安全,為了解決這些問題,我們需要阻止開發者使用一些,例如瀏覽器的window對象,跳轉頁面、操作DOM、動態執行腳本的開放性接口。

  我們可以使用客戶端系統的 JavaScript 引擎(iOS 下的 JavaScriptCore 框架,安卓下騰訊 x5 內核提供的 JsCore 環境),這個沙箱環境只提供純 JavaScript 的解釋執行環境,沒有任何瀏覽器相關接口,這就是小程序雙線程模型的由來。

三、組件系統

  我們知道小程序是有自己的組件的,這些基本組件就是基於 Exparser 框架。 Exparser 基於 WebComponents 的 ShadowDOM 模型,但是不依賴瀏覽器的原生支持,而且可在 純 JS 環境中運行。

1、Exparser框架

  Exparser是微信小程序的組件組織框架,內置在小程序基礎庫中,為小程序的各種組件提供基礎的支持。小程序內的所有組件,包括內置組件和自定義組件,都由Exparser組織管理。

  Exparser的主要特點包括以下幾點:

(1)基於Shadow DOM模型:模型上與WebComponents的ShadowDOM高度相似,但不依賴瀏覽器的原生支持,也沒有其他依賴庫;實現時,還針對性地增加了其他API以支持小程序組件編程。

(2)可在純JS環境中運行:這意味着邏輯層也具有一定的組件樹組織能力。

(3)高效輕量:性能表現好,在組件實例極多的環境下表現尤其優異,同時代碼尺寸也較小。

小程序中,所有節點樹相關的操作都依賴於Exparser,包括WXML到頁面最終節點樹的構建、createSelectorQuery調用和自定義組件特性等。

2、內置組件

  基於Exparser框架,小程序內置了一套組件,提供了視圖容器類、表單類、導航類、媒體類、開放類等幾十種組件。有了這么豐富的組件,再配合WXSS,可以搭建出任何效果的界面。在功能層面上,也滿足絕大部分需求。

3、原生組件

  在內置組件中,有一些組件並不完全在 Exparser 的渲染體系下,而是由客戶端原生參與組件的渲染。比如說 Map 組件,它渲染的層級比在 WebView 層渲染的普通組件要高。

四、運行機制

(1)啟動

  熱啟動:假如用戶已經打開過某小程序,然后在一定時間內再次打開該小程序,此時無需重新啟動,只需將后台態的小程序切換到前台,這個過程就是熱啟動;

  冷啟動:用戶首次打開或小程序被微信主動銷毀后再次打開的情況,此時小程序需要重新加載啟動,即冷啟動。

  小程序沒有重啟的概念

  當小程序進入后台,客戶端會維持一段時間的運行狀態,超過一定時間后(目前是5分鍾)會被微信主動銷毀

  當短時間內(5s)連續收到兩次以上收到系統內存告警,會進行小程序的銷毀

(2)銷毀

  只有當小程序進入后台一定時間,或者系統資源占用過高,才會被真正的銷毀。

(3)更新機制

  開發者在后台發布新版本之后,無法立刻影響到所有現網用戶,但最差情況下,也在發布之后 24 小時之內下發新版本信息到用戶。

  小程序每次冷啟動時,都會檢查是否有更新版本,如果發現有新版本,將會異步下載新版本的代碼包,並同時用客戶端本地的包進行啟動,即新版本的小程序需要等下一次冷啟動才會應用上。

  所以如果想讓用戶使用最新版本的小程序,可以利用 wx.getUpdateManager 做個檢查更新的功能:

checkNewVersion() { const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate((res) => { console.log('hasUpdate', res.hasUpdate); // 請求完新版本信息的回調
      if (res.hasUpdate) { updateManager.onUpdateReady(() => { this.setData({ hasNewVersion: true }); }); } }); }

五、小程序的技術實現

  小程序的UI視圖和邏輯處理是用多個webview實現的,邏輯處理的JS代碼全部加載到一個Webview里面,稱之為AppService,整個小程序只有一個,並且整個生命周期常駐內存,而所有的視圖(wxml和wxss)都是單獨的Webview來承載,稱之為AppView

  所以一個小程序打開至少就會有2個webview進程,正式因為每個視圖都是一個獨立的webview進程,考慮到性能消耗,小程序不允許打開超過5個層級的頁面,當然同是也是為了體驗更好。

1、AppService

  可以理解AppService即一個簡單的頁面,主要功能是負責邏輯處理部分的執行,底層提供一個WAService.js的文件來提供各種api接口,主要是以下幾個部分:

  消息通信封裝為WeixinJSBridge(開發環境為window.postMessage, IOS下為WKWebview的window.webkit.messageHandlers.invokeHandler.postMessage,android下用WeixinJSCore.invokeHandler)

  日志組件Reporter封裝

  wx對象下面的api方法

  全局的App,Page,getApp,getCurrentPages等全局方法

  還有就是對AMD模塊規范的實現

  然后整個頁面就是加載一堆JS文件,包括小程序配置config,上面的WAService.js(調試模式下有asdebug.js),剩下就是我們自己寫的全部的js文件,一次性都加載。

2、線上環境

  而在上線后是應用部分會打包為2個文件,名稱app-config.json和app-service.js,然后微信會打開webview去加載。線上部分應該是微信自身提供了相應的模板文件,在壓縮包里沒有找到。

  WAService.js(底層支持)

  app-config.json(應用配置)

  app-service.js(應用邏輯)

  然后運行在JavaScriptCore引擎里面。

3、AppView

  這里可以理解為h5的頁面,提供UI渲染,底層提供一個WAWebview.js來提供底層的功能,具體如下:

  消息通信封裝為WeixinJSBridge(開發環境為window.postMessage, IOS下為WKWebview的window.webkit.messageHandlers.invokeHandler.postMessage,android下用WeixinJSCore.invokeHandler)

  日志組件Reporter封裝

  wx對象下的api,這里的api跟WAService里的還不太一樣,有幾個跟那邊功能差不多,但是大部分都是處理UI顯示相關的方法

  小程序組件實現和注冊

  VirtualDOM,Diff和Render UI實現

  頁面事件觸發

  在此基礎上,AppView有一個html模板文件,通過這個模板文件加載具體的頁面,這個模板主要就一個方法,$gwx,主要是返回指定page的VirtualDOM,而在打包的時候,會事先把所有頁面的WXML轉換為ViirtualDOM放到模板文件里,而微信自己寫了2個工具wcc(把WXML轉換為VirtualDOM)和wcsc(把WXSS轉換為一個JS字符串的形式通過style標簽append到header里)。

4、Service和View通信

  使用消息publish和subscribe機制實現兩個Webview之間的通信,實現方式就是統一封裝一個WeixinJSBridge對象,而不同的環境封裝的接口不一樣,具體實現的技術如下:

(1)windows環境

  通過window.postMessage實現(使用chrome擴展的接口注入一個contentScript.js,它封裝了postMessage方法,實現webview之間的通信,並且也它通過chrome.runtime.connect方式,也提供了直接操作chrome native原生方法的接口)

發送消息:window.postMessage(data, ‘*’); // data里指定 webviewID 接收消息:window.addEventListener(‘message’, messageHandler);// 消息處理並分發,同樣支持調用nwjs的原生能力。

(2)IOS

  通過 WKWebview的window.webkit.messageHandlers.NAME.postMessage實現微信navite代碼里實現了兩個handler消息處理器:

  invokeHandler: 調用原生能力

  publishHandler: 消息分發

六、性能優化

  主要的優化策略可以歸納為三點:

(1)精簡代碼,降低WXML結構和JS代碼的復雜性;

(2)合理使用setData調用,減少setData次數和數據量;

(3)必要時使用分包優化。

1、setData 工作原理

  小程序的視圖層目前使用 WebView 作為渲染載體,而邏輯層是由獨立的 JavascriptCore 作為運行環境。

  在架構上,WebView 和 JavascriptCore 都是獨立的模塊,並不具備數據直接共享的通道。

  當前,視圖層和邏輯層的數據傳輸,實際上通過兩邊提供的 evaluateJavascript 所實現。即用戶傳輸的數據,需要將其轉換為字符串形式傳遞,同時把轉換后的數據內容拼接成一份 JS 腳本,再通過執行 JS 腳本的形式傳遞到兩邊獨立環境。

  而 evaluateJavascript 的執行會受很多方面的影響,數據到達視圖層並不是實時的。

2、常見的 setData 操作錯誤

(1)頻繁的去 setData

  在我們分析過的一些案例里,部分小程序會非常頻繁(毫秒級)的去setData,其導致了兩個后果:Android下用戶在滑動時會感覺到卡頓,操作反饋延遲嚴重,因為 JS 線程一直在編譯執行渲染,未能及時將用戶操作事件傳遞到邏輯層,邏輯層亦無法及時將操作處理結果及時傳遞到視圖層;渲染有出現延時,由於 WebView 的 JS 線程一直處於忙碌狀態,邏輯層到頁面層的通信耗時上升,視圖層收到的數據消息時距離發出時間已經過去了幾百毫秒,渲染的結果並不實時;

(2)每次 setData 都傳遞大量新數據

  由setData的底層實現可知,我們的數據傳輸實際是一次 evaluateJavascript 腳本過程,當數據量過大時會增加腳本的編譯執行時間,占用 WebView JS 線程

(3)后台態頁面進行setData

  當頁面進入后台態(用戶不可見),不應該繼續去進行setData,后台態頁面的渲染用戶是無法感受的,另外后台態頁面去setData也會搶占前台頁面的執行。

參考文章:

微信小程序的底層架構原理—技術干貨!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM