初探微信小程序架構原理

前言

小程序的主要開發語言是 JavaScript ,雖然有與網頁開發有類似性可是還有必定的區別javascript

  • 網頁開發渲染線程和腳本線程是互斥的,也是爲何長時間的腳本運行可能會致使頁面失去響應
  • 小程序中,邏輯層和渲染層是分開的,雙線程同時運行。渲染層的界面使用 WebView 進行渲染;邏輯層採用 JSCore 運行 JavaScript 代碼
  • 網頁開發面對的主要是瀏覽器及移動端瀏覽器 WebView
  • 小程序開發面對的是兩大操做系統 iOS 和 Android 的 微信客戶端,因此開發時候須要注意的是微信客戶端的版本號和小程序API 支持的基礎庫版本號

渲染非原生組件以及腳本執行環境的區別以下css

運行環境 邏輯層 渲染層
Android V8 Chromium定製內核
iOS JSCore WKWebView
小程序開發者工具 NWJS Chrome WebView

小程序宿主環境

在前言中提到小程序的宿主環境爲微信客戶端,因此藉助宿主環境提供的能力,能夠完成許多普通網頁沒法完成的功能html

渲染層和邏輯層

首先,咱們來簡單瞭解下小程序的運行環境。小程序的運行環境分紅渲染層和邏輯層,其中 WXML 模板和 WXSS 樣式工做在渲染層,JS 腳本工做在邏輯層。java

小程序的渲染層和邏輯層分別由2個線程管理:渲染層的界面使用了 WebView 進行渲染;邏輯層採用 JsCore 線程運行 JS 腳本。一個小程序存在多個界面,因此渲染層存在多個 WebView 線程,這兩個線程的通訊會經由微信客戶端作中轉,邏輯層發送網絡請求也經由 Native 轉發,小程序的通訊模型下圖所示。node

mini01.png

視圖和邏輯通訊

多 WebView 模式下,每個 WebView 都有一個獨立的 JSContext,那視圖和邏輯是如何進行通信?以下圖雙線程生命週期所示。git

mini02.png

相對於瀏覽器雙線程模型github

  • 更加安全,由於微信小程序阻止開發者使用一些瀏覽器提供的一些功能,如操做DOM、動態執行腳本等
  • 不用等待瀏覽器主線程去下載並解析 html,遇到 JS 腳本還會阻塞,影響視圖渲染,形成白屏
  • 缺點是雙線程若是頻繁的通訊,操做 setDate 更新視圖,對性能消耗特別嚴重,例如拖拽、滾動等

總體架構

當咱們對 View 層進行事件操做後,會經過 WeixinJSBridge 將數據傳遞到 Native 系統層。Native 系統層決定是否要用 native 處理,而後丟給邏輯層進行用戶的邏輯代碼處理。邏輯層處理後將數據經過 WeixinJSBridge 返給 View 層。View 渲染更新視圖,以下圖所示。web

mini03.jpg

編譯原理

微信開者工具模擬器運行的代碼是通過本地預處理、本地編譯,才能看見的頁面。而微信客戶端運行的代碼是額外通過服務器編譯的。只有通過編譯才能識別並運行小程序的代碼。咱們先來看一下小程序文件的基本結構npm

  • .wxml 頁面結構
  • .wxss 頁面樣式
  • .js 頁面邏輯
  • .json 頁面相關配置按照【約定優於配置】的原則

接下來咱們打開小程序開發者工具,點擊左上角微信開發者工具 >> 調試 >> 調試微信開發者工具,看到以下界面(官網組件demo)json

mini04.jpg

能夠看到渲染層和邏輯層是兩個 webview,第一個對應的 webview 是渲染層,每一個頁面都有一個webview,而邏輯層的 appservice 是隻有一個。

而後咱們接着往下看 webview 中究竟是什麼?打開 webview 發現 iframe 標籤是空的,但咱們想要查看怎麼辦,打開調試面板(console)下面輸入

// 第一步先找到全部的webview
document.getElementsByTagName('webview')
// 第二步找到第一個渲染層頁面用開發工具命令打開
document.getElementsByTagName('webview')[0].showDevTools(true,null)
複製代碼

這個時候咱們會看到以下窗口,這個頁面就是咱們渲染層的頁面結構了。

mini05.jpg 既然能看到每個渲染層,確定也能看到邏輯層代碼,在開發者工具控制檯中輸入 document,出現以下頁面

mini06.jpg

接下來咱們看一下微信小程序使用的基礎庫文件。在開發者工具控制檯中輸入 openVendor() 就會打開本地小程序的 WeappVendor 目錄,有幾個重要的文件

  • wcc 編譯器負責將 wxml 編譯成 js 文件
  • wcsc 編譯器負責將 wxss 文件編譯成 js 文件
  • xxx.wxvpkg 是不一樣版本的小程序基礎庫,主要包含小程序基礎庫 WAServiceWAWebview,這塊後續分析。

wcc的做用

一、新建一個名爲 compiler.js 的文件,並寫入如下代碼

const fs = require("fs");
const miniprogramCompiler = require("miniprogram-compiler");

const path = require("path");
let JsCompiler = miniprogramCompiler.wxmlToJs(path.join(__dirname));
let cssCompiler = miniprogramCompiler.wxssToJs(path.join(__dirname));
fs.writeFileSync("wxml.js", JsCompiler);
fs.writeFileSync("wxcss.js", cssCompiler);
複製代碼

二、執行 npm install miniprogram-compiler 三、執行 node compiler.js,會在同目錄生成 wxmlwxcss 兩個 JS 文件 四、新建 index.wxml 並寫入以下代碼

<view class="box">
  <text class="box-text">{{ text }}</text>
</view>
複製代碼

五、新建 index.html 引入文件 wxml.js,以下代碼

<script src="./wxml.js"></script>
<script> const res = $gwx("index.wxml"); const virtualTree = res({ text: 'data數據' }); console.log(virtualTree); </script>
複製代碼

用瀏覽器打開 index.html 控制檯會輸出相似 Virtual DOM 的對象

{
  "tag":"wx-page",
  "children":[{
    "tag":"wx-view",
    "attr":{ "class":"box"},
    "children":[{
        "tag":"wx-text",
        "attr":{ "class":"box-text" },
        "children":[ "data數據" ],
        "raw":{},
        "generics":{}
    }],
    "raw":{},
    "generics":{}
  }]
}
複製代碼

wcc的做用就是:

  • 執行 wcc 編譯 wxml 生成相關頁面註冊代碼,並記錄標籤的屬性及其值(生成 JS 文件)
  • 這個文件主體是一個 $gwx() 函數,接收兩個參數 path (頁面 wxml 路徑)和 global(頂層對象)

wcsc的做用

  • wcsc 編譯 wxss 獲得一個 js 文件
  • 添加尺寸單位rpx轉換,可根據屏幕寬度自適應
  • 提供 setCssToHead 方法將轉換後的 css 內容添加到 header

xxx.wxvpkg

上面提到過開發者工具中輸入openVendor 以後會看到不少 .wxvpkg 文件,這是小程序的基礎庫,主要有兩部分組成 WAServiceWAWebview

  • WAWebview:小程序視圖層基礎庫,提供視圖層基礎能力
  • WAService:小程序邏輯層基礎庫,提供邏輯層基礎能力

微信小程序基礎庫版本是不斷更新的,當前我本地的最高版本爲 2.14.1.wxvpkg ,咱們能夠利用unwxapkg 解壓 2.14.1.wxvpkg。解壓後的目錄爲

├── WAAutoService.js
├── WAAutoWebview.js
├── WAGame.js
├── WAGameSubContext.js
├── WAGameVConsole.html
├── WAGfxEmsc.js
├── WAGfxEmsc.wasm
├── WAPageFrame.html
├── WAPerf.js
├── WARemoteDebug.js
├── WAService.js
├── WAServiceMainContext.js
├── WASourceMap.js
├── WASubContext.js
├── WAVConsole.js
├── WAVersion.json
├── WAWKWorker.html
├── WAWebview.js
├── WAWidget.js
└── WAWorker.js
複製代碼

其餘文件先忽略,咱們先看一下 webview 引用的兩個基礎文件源碼概覽 WAWebviewWAService

其中,WAWebview 最主要的幾個部分:

  • Foundation:基礎模塊(發佈訂閱、通訊橋樑 ready 事件)
  • WeixinJSBridge:消息通訊模塊(js 和 native 通信) Webview 和 Service都有相同的一套
  • exparser:組件系統模塊,實現了一套自定義的組件模型,好比實現了 wx-view
  • __virtualDOM__:虛擬 Dom 模塊
  • __webViewSDK__:WebView SDK 模塊
  • Reporter:日誌上報模塊(異常和性能統計數據)

其中,WAService 最主要的幾個部分:

  • Foundation:基礎模塊
  • WeixinJSBridge:消息通訊模塊(js 和 native 通信) Webview 和 Service都有相同的一套
  • WeixinNativeBuffer:原生緩衝區
  • WeixinWorker:Worker 線程
  • JSContext:JS Engine Context
  • Protect:JS 保護的對象
  • __subContextEngine__:提供 App、Page、Component、Behavior、getApp、getCurrentPages 等方法

Foundation 模塊

基礎模塊提供環境變量 env、發佈訂閱 EventEmitter、配置/基礎庫/通訊橋 Ready 事件。

mini07.jpg

Exparser 模塊

微信小程序的組件組織框架,內置在小程序基礎庫中,爲小程序的各類組件提供基礎的支持。小程序內的全部組件,包括內置組件和自定義組件,都由 Exparser 組織管理。Exparser 的組件模型與 WebComponents 標準中的 ShadowDOM 高度類似。Exparser 會維護整個頁面的節點樹相關信息,包括節點的屬性、事件綁定等,至關於一個簡化版的 Shadow DOM 實現。

mini08.jpg

Virtual DOM 模塊

生成 wx-element 對象,和 virtual-dom 相似

mini09.jpg

WeixinJSBridge 模塊

提供了視圖層 JS 與 Native、視圖層與邏輯層之間消息通訊的機制,以下圖幾個方法:

mini10.jpg

首次渲染流程

經過上面這些主體結構和函數,對小程序應該有大體的瞭解,那麼咱們把上面串起來看一下首次渲染的流程

  • 打開微信開發者工具
  • 點擊左上角調試微信開發者工具
  • 調試面板輸入 document.getElementsByTagName('webview')[0].showDevTools(true)

mini11.jpg

上圖中最後標註的代碼

var decodeName = decodeURI("./pages/index/index.wxml");
var generateFunc = $gwx(decodeName);
if (generateFunc) {
    var CE = (typeof __global === 'object') ? (window.CustomEvent || __global.CustomEvent) : window.CustomEvent;
    document.dispatchEvent(new CE("generateFuncReady", {
        detail: {
            generateFunc: generateFunc
        }
    })) __global.timing.addPoint('PAGEFRAME_GENERATE_FUNC_READY', Date.now())
} else {
    document.body.innerText = decodeName + " not found"console.error(decodeName + " not found")
};
複製代碼

因此初次渲染流程以下

  • 利用 $gwx 建立相似虛擬 DOM 的節點樹
  • 建立自定義事件 window.CustomEvent(CE)
  • 分發自定義事件 document.dispatchEvent
  • 在 WAWebview 監聽這個事件,而後經過 WeixinJSBridge 通知 JS 邏輯層視圖已經準備好
c = function() {
    setTimeout(function() {
          ! function() {
            var e = arguments;
            r(function() {
              WeixinJSBridge.publish.apply(WeixinJSBridge, o(e))
            })
        }("GenerateFuncReady", {})
    }, 20)
  }
  document.addEventListener("generateFuncReady", c)
複製代碼
  • 最後 JS 邏輯層將數據給 Webview 視圖層,進行首次渲染

mini12.jpeg

通訊原理

小程序邏輯層和渲染層的通訊會由 Native (微信客戶端)作中轉,邏輯層發送網絡請求也經由 Native 轉發。

視圖層組件:

內置組件中有部分組件(map、video等)是利用到客戶端原生提供的能力,那是怎麼通訊的呢?iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓則是往 WebView 的 window 對象注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個兼容層,主要提供了調用(invoke)和監聽(on)這兩種方法。

邏輯層接口:

iOS 平臺往 JavaScripCore 框架注入一個全局的原生方法,而安卓方面則是跟渲染層一致。 不論視圖層(部分組件)仍是邏輯層,開發者都是間接地調用客戶端原生底層的能力。

寫在最後

最後,推薦一套TS全系列的教程吧。近期在提高TS,收藏了一套很不錯的教程,無償分享給xdm www.yidengxuetang.com/pub-page/in…

相關文章
相關標籤/搜索