微信小程序架構原理

  • js
  • json 配置文件
  • wxml 小程序專用 xml 文件
  • wxss 小程序專用 css 文件
<view>
<text>{{ text }}/<text>
/<view>
Page({
data:{
text:"這是一個頁面"
},
onLoad:function(options){
// 頁面初始化 options為頁面跳轉所帶來的參數
},
// ........
})

微信小程序只能通過其 mvvm 的模板語法來動態改變頁面,本身 js 並不支持 BOM 和 DOM 操作。

在 mac 端直接解壓應用 發現 app.nw 文件夾,即開發工具源碼。可以知道該項目由 nw.js 編寫; 在 package.json 文件下找到應用入口:app/html/index.html。入口 js 為 dist/app.js 我們可以看到整個編輯器的大致邏輯。

但我們關心的是構建過程,在 weapp 文件夾下存在 build.js 文件。沒有找到有用的信息,只看到了 upload 模塊,包括對大小限制,上傳包命名。

為此懷疑,微信小程序本身和 RN 類似。是在服務端打包成 native 語言的。但是通過 android 邊框測試發現,微信小程序根本不是 native 原生內容。

原生界面效果:

微信小程序架構原理

編譯過程

繼續在 trans 文件夾下發現了編譯模板。

  • transWxmlToJs wxml 轉 js
  • transWxssToCss wxss 轉 css
  • transConfigToPf 模板頁配置
  • transWxmlToHtml wxml 轉 html
  • transManager 管理器

用到的內容:

  • 發現用到了一個模板:app.nw/app/dist/weapp/tpl/pageFrameTpl.js, app.mw/app.dist.weapp/tpl/appserviceTpl.js
  • wcc 可執行程序,wcc 用於轉轉 wxml 中的自定義 tag 為 virtual_dom
  • wcsc 可執行程序,用於將 wxss 轉為 view 模塊使用的 css 代碼,使用方式為 wcsc xxx.wxss

在模板中,我們發現使用了 WAWebview.js 文件,WAService.js文件。 在 transWxmlToJs 中我們發現一段 generateFuncReady 事件的函數。對比註冊該事件的函數在 WAWebview.js 中。

我們嘗試使用 wcc 對input.xml 文件進行編譯。

wcc -d input.xml

生成了一段腳本:

window.__wcc_version__ = 'v0.6vv_20161230_fbi'
var $gwxc
var $gaic =
$gwx = function (path, global) {
function _(a, b) {
b && a.children.push(b);
}
....

通過代碼我們發現,調用 $gwx 函數會再生成一個有返回值的函數(前提是 path 填寫正確);於是我們執行如下代碼:

$gwx("input.xml")("test")

得出如下內容:

{
"tag": "wx-page",
"children": [
{
"tag": "wx-view",
"attr": {
"class": "section"
},
"children": [
{
"tag": "wx-input",
"attr": {
"autoFocus": true,
"placeholder": "這是一個可以自動聚焦的input"
},
"children": []
}
]
}
]
}

這應該是一個類似 Virtual dom 的對象,交給了 WAWebivew.js 來渲染,標籤名為 wx-view, wx-input。

WAWebview.js

  1. 代碼在最一開始提供的是兼容性工具,還有一個 WeixinJSBridge 引入。
  2. 接下來是一個 Reporter 對象,它的作用就是發送錯誤和性能統計數據給後臺。
微信小程序架構原理

  1. wx 核心對象,包含了 wx 對象下的 api。但是這裡的 api 數量遠遠少於官方的 api 文檔數量。
微信小程序架構原理

我們可以在代碼裡面發現,wx 下注冊的 api 最終都會調用 WeixinJSBridge 方法。這個方法應該是在打包的時候端上注入的。我們也可以在 WAServeice.js 中找到該方法的定義。

微信小程序架構原理

所以我們得到了一個結論,WAService.js 是編輯器用來接受 wx 方法回調的代碼。

  1. wxparser 對象,提供 dom 到 wx element 對象之間的映射操作,提供元素操作管理和事件管理功能。
  2. 之後代碼是對 exparser 對象的處理,包括註冊 WeixinJSBridge 全局事件,Virtual dom 算法實現,樣式注入等。介紹幾個組件重要的內容
  • exparser.registerBehavior 註冊組件基礎行為,供組件繼承。
微信小程序架構原理

  • exparser.registerElement 為各種內置組件,註冊模板,行為,屬性,監聽器等內容
微信小程序架構原理

這裡我們觀察到,組件:wx-video, wx-canvas, wx-contact-button, wx-map, wx-textarea 等 behaviors 都含有 "wx-native" 屬性。這是不是意味著,這類組件都是 native 原生實現的呢。我們打開邊框檢查,發現這類組件確實都是原生的組件。

微信小程序架構原理

綜上,微信小程序的界面有部分組件使用原生方式實現的,native 組件層在 WebView 層之上。大部分還是用前端實現的,這樣解釋了微信小程序的一個bug。

微信小程序架構原理

因為 scroll-view 是前端實現,在裡面使用 native 組件,這樣就無法監聽滾動了。

WeixinJSBridge

組件是需要數據來渲染的,查看文檔我們知道發送請求的 api 為 wx.request;通過上面分析,我們知道 wx.request 實際調用的是 WeixinJSBridge。現在我們看看 WeixinJSBridge

微信小程序架構原理

WeixinJSBridge 真正發送處理數據請求的是這段代碼;如果當前環境是 ios, 那麼調用 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage。如果所處環境是 android 則調用 WeixinJSCore.invokeHandler (調用的時候,默認會帶上當前 webviewID)。

WAService.js

在對 WeixinJSBridge.js 分析中,我們並沒有發現前端的通訊功能,路由能力,數據綁定等內容。進一步查看找到了一個 WAService.js 文件。 查看 WAService.js 文件源碼:

  1. 在代碼最開始,跟 WAWebview.js 一樣的 WeixinJSBridge 兼容模塊
  2. 然後是跟 WAWebview.js 一樣的 Reporter 模塊。
  3. 比 WAWebview.js 中 wx 功能更為豐富 wx 接口模塊。(剩餘部分 wx api 都在這裡)
  4. appServiceEngine 模塊,提供 Page,App,GetApp 接口
  5. 為 window 對象添加 AMD 接口 require define

綜上,WAService.js 主要實現的功能:

  • App( ) 小程序的入口;Page( ) 頁面的入口
  • wx API;
  • 頁面有的作用域,提供模塊化能力
  • 數據綁定、事件分發、生命週期管理、路由管理

到這裡我們得出結論,小程序的架構方案:

微信小程序架構原理

整個小程序由兩個 webview 組成,代碼分為 UI 層和邏輯層。UI 層運行在第一個 WebView 當中,執行 DOM 操作和交互事件的響應,裡面是 WAWebview.js 代碼及編譯後的內容。邏輯層執行在(第二個webview 中)獨立的 JS 引擎中(iOS:JavaScriptCore, android:X5 JS解析器;統稱 JSCore;開發工具中,nwjs Chrome 內核),WAService.js 代碼和業務邏輯。

當我們對 view 層進行事件操作後,會通過 WeixinJSBridge 將數據傳遞到 Native 系統層。Native 系統層決定是否要用 native 處理,然後丟給 邏輯層進行用戶的邏輯代碼處理。邏輯層處理完畢後會將數據通過 WeixinJSBridge 返給 View 層。View 渲染更新視圖。

架構的討論

微信的這種架構,對邏輯和UI進行了完全隔離,小程序邏輯和UI完全運行在2個獨立的Webview裡面來處理。那麼這麼做的好處是啥?總感覺更加麻煩了。除了小程序外,還有人採用這種架構設計麼?

在網上搜索了一下,目前使用這種架構的項目還真有一個:去哪兒最新的 YIS 框架

YIS 採取了類似小程序的架構,分為邏輯層和UI層。UI 層運行在 WebView 中,而邏輯層運行在獨立的 JS 引擎中。相應地,整個應用的代碼,也分為兩個大的部分,一部分運行在 WebView 中,一部分運行在JS引擎中。JS引擎計算DOM結構輸出給WebView,WebView轉發用戶的點擊事件給JS引擎。

該項目做法和小程序十分類似,唯一缺少的就是沒有 native 的組件吧。然而官方文檔上也沒有任何介紹,為什麼要這麼做,只是說更流暢了。

一些看法

傳統 web 頁面顯示需要經歷一下幾個步驟:

  1. webview 初始化
  2. 加載 HTML, CSS, JS
  3. 編譯 JS
  4. Render 計算
  5. DOM Path

而利用小程序架構後,我們就可以將上述過程拆解成兩部分並行執行: webview 部分:

  1. webview 初始化
  2. 加載 HTML,CSS, JS (經過拆分後,體積大幅度減小)
  3. 編譯 JS
  4. 等待頁面需要的數據
  5. 反序列化數據
  6. 執行 Patch
  7. 渲染頁面
  8. 等待更多消息

jscore 部分:

  1. 初始化
  2. 加載框架 js 代碼
  3. 編譯 js
  4. 加載業務邏輯 js 代碼
  5. 編譯 js
  6. 計算首屏虛擬 DOM 結構
  7. 序列化數據,傳輸
  8. 等待 webview 消息,或者 Native 消息

這樣渲染進程和邏輯進程分離,並行處理:加速首屏渲染速度;避免單線程模型下,js 運算時間過長,UI 出現卡頓。 完全採用數據驅動的方式,不能直接操作 DOM,利用定製開發規範的方式避免低質量的代碼的出現。

當然這種架構方案也有一定的缺點:

  1. 不能靈活操作 DOM,無法實現較為複雜的效果
  2. 部分和 NA 相關的視圖有使用限制,如微信的 scrollView 內不能有 textarea。
  3. 頁面大小、打開頁面數量都受到限制
  4. 需要單獨開發適配,不能複用現有代碼資源。
  5. 在 jscore 中JS 體積比較大的情況下,其初始化時間會產生影響。
  6. 傳輸數據中,序列化和反序列化耗時需要考慮

希望本文能幫助到您!

點贊+轉發,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓-_-)

關注 {我},享受文章首發體驗!

每週重點攻克一個前端技術難點。更多精彩前端內容私信 我 回覆“教程”

原文鏈接:http://eux.baidu.com/blog/fe/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%9E%B6%E6%9E%84%E5%8E%9F%E7%90%86

"


分享到:


相關文章: