微信小程序包含下面四種文件:
- js
- json 配置文件
- wxml 小程序專用 xml 文件
- wxss 小程序專用 css 文件
<view> <text class="window">{{ 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
- 代碼在最一開始提供的是兼容性工具,還有一個 WeixinJSBridge 引入。
- 接下來是一個 Reporter 對象,它的作用就是發送錯誤和性能統計數據給后臺。
- wx 核心對象,包含了 wx 對象下的 api。但是這里的 api 數量遠遠少于官方的 api 文檔數量。
我們可以在代碼里面發現,wx 下注冊的 api 最終都會調用 WeixinJSBridge 方法。這個方法應該是在打包的時候端上注入的。我們也可以在 WAServeice.js 中找到該方法的定義。
所以我們得到了一個結論,WAService.js 是編輯器用來接受 wx 方法回調的代碼。
- wxparser 對象,提供 dom 到 wx element 對象之間的映射操作,提供元素操作管理和事件管理功能。
- 之后代碼是對 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。
微信官方文檔:
因為%20scroll-view%20是前端實現,在里面使用%20native%20組件,這樣就無法監聽滾動了。
WeixinJSBridge
組件是需要數據來渲染的,查看文檔我們知道發送請求的%20api%20為%20wx.request;通過上面分析,我們知道%20wx.request%20實際調用的是%20WeixinJSBridge。現在我們看看%20WeixinJSBridge
WeixinJSBridge 真正發送處理數據請求的是這段代碼;如果當前環境是 IOS, 那么調用 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage。如果所處環境是 android 則調用 WeixinJSCore.invokeHandler (調用的時候,默認會帶上當前 webviewID)。
WAService.js
在對 WeixinJSBridge.js 分析中,我們并沒有發現前端的通訊功能,路由能力,數據綁定等內容。進一步查看找到了一個 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 主要實現的功能:
- 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 頁面顯示需要經歷一下幾個步驟:
- webview 初始化
- 加載 HTML, CSS, JS
- 編譯 JS
- Render 計算
- DOM Path
而利用小程序架構后,我們就可以將上述過程拆解成兩部分并行執行: webview 部分:
- webview 初始化
- 加載 HTML,CSS, JS (經過拆分后,體積大幅度減小)
- 編譯 JS
- 等待頁面需要的數據
- 反序列化數據
- 執行 Patch
- 渲染頁面
- 等待更多消息
jscore 部分:
- 初始化
- 加載框架 js 代碼
- 編譯 js
- 加載業務邏輯 js 代碼
- 編譯 js
- 計算首屏虛擬 DOM 結構
- 序列化數據,傳輸
- 等待 webview 消息,或者 Native 消息
這樣渲染進程和邏輯進程分離,并行處理:加速首屏渲染速度;避免單線程模型下,js 運算時間過長,UI 出現卡頓。 完全采用數據驅動的方式,不能直接操作 DOM,利用定制開發規范的方式避免低質量的代碼的出現。
當然這種架構方案也有一定的缺點:
- 不能靈活操作 DOM,無法實現較為復雜的效果
- 部分和 NA 相關的視圖有使用限制,如微信的 scrollView 內不能有 textarea。
- 頁面大小、打開頁面數量都受到限制
- 需要單獨開發適配,不能復用現有代碼資源。
- 在 jscore 中JS 體積比較大的情況下,其初始化時間會產生影響。
- 傳輸數據中,序列化和反序列化耗時需要考慮
希望本文能幫助到您!
點贊+轉發,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓-_-)
關注 {我},享受文章首發體驗!
每周重點攻克一個前端技術難點。更多精彩前端內容私信 我 回復“教程”
原文鏈接: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
作者:田光宇