微信小程序包含下面四種文件:
- js
- json 配置文件
- wxml 小程序?qū)S?xml 文件
- wxss 小程序?qū)S?css 文件
<view> <text class="window">{{ text }}</text> </view> Page({ data:{ text:"這是一個頁面" }, onLoad:function(options){ // 頁面初始化 options為頁面跳轉(zhuǎn)所帶來的參數(shù) }, // ........ })
微信小程序只能通過其 mvvm 的模板語法來動態(tài)改變頁面,本身 js 并不支持 BOM 和 DOM 操作。
從開發(fā)工具看微信小程序架構(gòu)
在 mac 端直接解壓應(yīng)用 發(fā)現(xiàn) App.nw 文件夾,即開發(fā)工具源碼。可以知道該項(xiàng)目由 nw.js 編寫; 在 package.json 文件下找到應(yīng)用入口:app/html/index.html。入口 js 為 dist/app.js 我們可以看到整個編輯器的大致邏輯。
但我們關(guān)心的是構(gòu)建過程,在 weapp 文件夾下存在 build.js 文件。沒有找到有用的信息,只看到了 upload 模塊,包括對大小限制,上傳包命名。
為此懷疑,微信小程序本身和 RN 類似。是在服務(wù)端打包成 native 語言的。但是通過 Android 邊框測試發(fā)現(xiàn),微信小程序根本不是 native 原生內(nèi)容。
原生界面效果:

編譯過程
繼續(xù)在 trans 文件夾下發(fā)現(xiàn)了編譯模板。
- transWxmlToJs wxml 轉(zhuǎn) js
- transWxssToCss wxss 轉(zhuǎn) css
- transConfigToPf 模板頁配置
- transWxmlToHtml wxml 轉(zhuǎn) html
- transManager 管理器
用到的內(nèi)容:
- 發(fā)現(xiàn)用到了一個模板:app.nw/app/dist/weapp/tpl/pageFrameTpl.js, app.mw/app.dist.weapp/tpl/appserviceTpl.js
- wcc 可執(zhí)行程序,wcc 用于轉(zhuǎn)轉(zhuǎn) wxml 中的自定義 tag 為 virtual_dom
- wcsc 可執(zhí)行程序,用于將 wxss 轉(zhuǎn)為 view 模塊使用的 css 代碼,使用方式為 wcsc xxx.wxss
在模板中,我們發(fā)現(xiàn)使用了 WAWebview.js 文件,WAService.js文件。 在 transWxmlToJs 中我們發(fā)現(xiàn)一段 generateFuncReady 事件的函數(shù)。對比注冊該事件的函數(shù)在 WAWebview.js 中。
我們嘗試使用 wcc 對input.xml 文件進(jìn)行編譯。
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); } ....
通過代碼我們發(fā)現(xiàn),調(diào)用 $gwx 函數(shù)會再生成一個有返回值的函數(shù)(前提是 path 填寫正確);于是我們執(zhí)行如下代碼:
$gwx("input.xml")("test")
得出如下內(nèi)容:
{ "tag": "wx-page", "children": [ { "tag": "wx-view", "attr": { "class": "section" }, "children": [ { "tag": "wx-input", "attr": { "autoFocus": true, "placeholder": "這是一個可以自動聚焦的input" }, "children": [] } ] } ] }
這應(yīng)該是一個類似 Virtual dom 的對象,交給了 WAWebivew.js 來渲染,標(biāo)簽名為 wx-view, wx-input。
WAWebview.js
- 代碼在最一開始提供的是兼容性工具,還有一個 WeixinJSBridge 引入。
- 接下來是一個 Reporter 對象,它的作用就是發(fā)送錯誤和性能統(tǒng)計數(shù)據(jù)給后臺。

- wx 核心對象,包含了 wx 對象下的 api。但是這里的 api 數(shù)量遠(yuǎn)遠(yuǎn)少于官方的 api 文檔數(shù)量。

我們可以在代碼里面發(fā)現(xiàn),wx 下注冊的 api 最終都會調(diào)用 WeixinJSBridge 方法。這個方法應(yīng)該是在打包的時候端上注入的。我們也可以在 WAServeice.js 中找到該方法的定義。

所以我們得到了一個結(jié)論,WAService.js 是編輯器用來接受 wx 方法回調(diào)的代碼。
- wxparser 對象,提供 dom 到 wx element 對象之間的映射操作,提供元素操作管理和事件管理功能。
- 之后代碼是對 exparser 對象的處理,包括注冊 WeixinJSBridge 全局事件,Virtual dom 算法實(shí)現(xiàn),樣式注入等。介紹幾個組件重要的內(nèi)容
- exparser.registerBehavior 注冊組件基礎(chǔ)行為,供組件繼承。

- exparser.registerElement 為各種內(nèi)置組件,注冊模板,行為,屬性,監(jiān)聽器等內(nèi)容

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

綜上,微信小程序的界面有部分組件使用原生方式實(shí)現(xiàn)的,native 組件層在 WebView 層之上。大部分還是用前端實(shí)現(xiàn)的,這樣解釋了微信小程序的一個bug。
微信官方文檔:
因?yàn)?20scroll-view%20是前端實(shí)現(xiàn),在里面使用%20native%20組件,這樣就無法監(jiān)聽滾動了。
WeixinJSBridge
組件是需要數(shù)據(jù)來渲染的,查看文檔我們知道發(fā)送請求的%20api%20為%20wx.request;通過上面分析,我們知道%20wx.request%20實(shí)際調(diào)用的是%20WeixinJSBridge。現(xiàn)在我們看看%20WeixinJSBridge
WeixinJSBridge 真正發(fā)送處理數(shù)據(jù)請求的是這段代碼;如果當(dāng)前環(huán)境是 IOS, 那么調(diào)用 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage。如果所處環(huán)境是 android 則調(diào)用 WeixinJSCore.invokeHandler (調(diào)用的時候,默認(rèn)會帶上當(dāng)前 webviewID)。
WAService.js
在對 WeixinJSBridge.js 分析中,我們并沒有發(fā)現(xiàn)前端的通訊功能,路由能力,數(shù)據(jù)綁定等內(nèi)容。進(jìn)一步查看找到了一個 WAService.js 文件。 查看 WAService.js 文件源碼:
- 在代碼最開始,跟 WAWebview.js 一樣的 WeixinJSBridge 兼容模塊
- 然后是跟 WAWebview.js 一樣的 Reporter 模塊。
- 比 WAWebview.js 中 wx 功能更為豐富 wx 接口模塊。(剩余部分 wx api 都在這里)
- appServiceEngine 模塊,提供 Page,App,GetApp 接口
- 為 window 對象添加 AMD 接口 require define
綜上,WAService.js 主要實(shí)現(xiàn)的功能:
- App( ) 小程序的入口;Page( ) 頁面的入口
- wx API;
- 頁面有的作用域,提供模塊化能力
- 數(shù)據(jù)綁定、事件分發(fā)、生命周期管理、路由管理
到這里我們得出結(jié)論,小程序的架構(gòu)方案:

整個小程序由兩個 webview 組成,代碼分為 UI 層和邏輯層。UI 層運(yùn)行在第一個 WebView 當(dāng)中,執(zhí)行 DOM 操作和交互事件的響應(yīng),里面是 WAWebview.js 代碼及編譯后的內(nèi)容。邏輯層執(zhí)行在(第二個webview 中)獨(dú)立的 JS 引擎中(iOS:JAVAScriptCore, android:X5 JS解析器;統(tǒng)稱 JSCore;開發(fā)工具中,nwjs Chrome 內(nèi)核),WAService.js 代碼和業(yè)務(wù)邏輯。
當(dāng)我們對 view 層進(jìn)行事件操作后,會通過 WeixinJSBridge 將數(shù)據(jù)傳遞到 Native 系統(tǒng)層。Native 系統(tǒng)層決定是否要用 native 處理,然后丟給 邏輯層進(jìn)行用戶的邏輯代碼處理。邏輯層處理完畢后會將數(shù)據(jù)通過 WeixinJSBridge 返給 View 層。View 渲染更新視圖。
架構(gòu)的討論
微信的這種架構(gòu),對邏輯和UI進(jìn)行了完全隔離,小程序邏輯和UI完全運(yùn)行在2個獨(dú)立的Webview里面來處理。那么這么做的好處是啥?總感覺更加麻煩了。除了小程序外,還有人采用這種架構(gòu)設(shè)計么?
在網(wǎng)上搜索了一下,目前使用這種架構(gòu)的項(xiàng)目還真有一個:去哪兒最新的 YIS 框架
YIS 采取了類似小程序的架構(gòu),分為邏輯層和UI層。UI 層運(yùn)行在 WebView 中,而邏輯層運(yùn)行在獨(dú)立的 JS 引擎中。相應(yīng)地,整個應(yīng)用的代碼,也分為兩個大的部分,一部分運(yùn)行在 WebView 中,一部分運(yùn)行在JS引擎中。JS引擎計算DOM結(jié)構(gòu)輸出給WebView,WebView轉(zhuǎn)發(fā)用戶的點(diǎn)擊事件給JS引擎。
該項(xiàng)目做法和小程序十分類似,唯一缺少的就是沒有 native 的組件吧。然而官方文檔上也沒有任何介紹,為什么要這么做,只是說更流暢了。
一些看法
傳統(tǒng) web 頁面顯示需要經(jīng)歷一下幾個步驟:
- webview 初始化
- 加載 HTML, CSS, JS
- 編譯 JS
- Render 計算
- DOM Path
而利用小程序架構(gòu)后,我們就可以將上述過程拆解成兩部分并行執(zhí)行: webview 部分:
- webview 初始化
- 加載 HTML,CSS, JS (經(jīng)過拆分后,體積大幅度減小)
- 編譯 JS
- 等待頁面需要的數(shù)據(jù)
- 反序列化數(shù)據(jù)
- 執(zhí)行 Patch
- 渲染頁面
- 等待更多消息
jscore 部分:
- 初始化
- 加載框架 js 代碼
- 編譯 js
- 加載業(yè)務(wù)邏輯 js 代碼
- 編譯 js
- 計算首屏虛擬 DOM 結(jié)構(gòu)
- 序列化數(shù)據(jù),傳輸
- 等待 webview 消息,或者 Native 消息
這樣渲染進(jìn)程和邏輯進(jìn)程分離,并行處理:加速首屏渲染速度;避免單線程模型下,js 運(yùn)算時間過長,UI 出現(xiàn)卡頓。 完全采用數(shù)據(jù)驅(qū)動的方式,不能直接操作 DOM,利用定制開發(fā)規(guī)范的方式避免低質(zhì)量的代碼的出現(xiàn)。
當(dāng)然這種架構(gòu)方案也有一定的缺點(diǎn):
- 不能靈活操作 DOM,無法實(shí)現(xiàn)較為復(fù)雜的效果
- 部分和 NA 相關(guān)的視圖有使用限制,如微信的 scrollView 內(nèi)不能有 textarea。
- 頁面大小、打開頁面數(shù)量都受到限制
- 需要單獨(dú)開發(fā)適配,不能復(fù)用現(xiàn)有代碼資源。
- 在 jscore 中JS 體積比較大的情況下,其初始化時間會產(chǎn)生影響。
- 傳輸數(shù)據(jù)中,序列化和反序列化耗時需要考慮
希望本文能幫助到您!
點(diǎn)贊+轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容(收藏不點(diǎn)贊,都是耍流氓-_-)
關(guān)注 {我},享受文章首發(fā)體驗(yàn)!
每周重點(diǎn)攻克一個前端技術(shù)難點(diǎn)。更多精彩前端內(nèi)容私信 我 回復(fù)“教程”
原文鏈接: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
作者:田光宇