微信小程序架構(gòu)分析 (中)
【引自第九程序的博客】本文探討一下小程序的 view 模塊和 service 模塊是如何構(gòu)成的。
打開微信 web 開發(fā)者工具,然后輸入 openVendor() 便會(huì)打開 WeappVendor這個(gè)目錄,這里包含了 view 模塊和 service 模塊使用的幾個(gè)核心文件:
- wcc 可執(zhí)行程序,用于將 wxml 轉(zhuǎn)為 view 模塊使用的 js 代碼,使用方式為wcc xxx.wxml
 - wcsc 可執(zhí)行程序,用于將 wxss 轉(zhuǎn)為 view 模塊使用的 css 代碼,使用方式為 wcsc xxx.wxss
 - WAService.js 提供 service 模塊大部分功能,下面會(huì)有詳細(xì)介紹
 - WAWebview.js 提供 view 模塊大部分功能,下面會(huì)有詳細(xì)介紹
 
view 頁面詳解
view 頁面的 template 如下:
- <!DOCTYPE html>
 - <html lang="zh-CN">
 - <head>
 - <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
 - <meta charset="UTF-8" />
 - <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
 - <script>
 - var __webviewId__;
 - </script>
 - <!-- percodes -->
 - <!--{{WAWebview}}-->
 - <!--{{reportSDK}}-->
 - <!--{{webviewSDK}}-->
 - <!--{{exparser}}-->
 - <!--{{components_js}}-->
 - <!--{{virtual_dom}}-->
 - <!--{{components_css}}-->
 - <!--{{allWXML}}-->
 - <!--{{eruda}}-->
 - <!--{{style}}-->
 - <!--{{currentstyle}}-->
 - <!--{{generateFunc}}-->
 - </head>
 - <body>
 - <div></div>
 - </body>
 - </html>
 
其中 <!-- percodes --> 會(huì)在 dev 模式開啟后被替換為一個(gè)時(shí)間錨點(diǎn),例如:
- <script>var pageFrameStartTime = new Date();</script>
 
<!--{{WAWebview}}--> 會(huì)被 WAWebview.js 內(nèi)代碼替換
<!--{{WAWebview}}--> 到 <!--{{generateFunc}}--> 之間暫時(shí)沒有被使用到
<!--{{generateFunc}}--> 會(huì)被 wcc 命令生成后的 js 代碼替換
除了上面這些,頁面上還會(huì)被插入頁面和應(yīng)用的 style 標(biāo)簽,如:
- <link rel="stylesheet" type="text/css" href="index.wxss">
 
這里的 wxss 文件包含的是原始 wxss 文件轉(zhuǎn)換后的 css
以及生成 DOM 的啟動(dòng)腳本:
- <script>
 - document.dispatchEvent(new CustomEvent("generateFuncReady", {
 - detail: {
 - generateFunc: $gwx('./page/index.wxml')
 - }
 - }))
 - </script>
 
WAWebview.js 文件中的各個(gè)模塊(行號(hào)為 jsbeautify 之后代碼行號(hào),開發(fā)者工具版本:092300):
- 1-77 行: WeixinJSBridge 對(duì)象兼容層,這個(gè)大概只會(huì)在調(diào)試時(shí)用到,因?yàn)殚_發(fā)時(shí)和運(yùn)行時(shí)頁面都會(huì)被后臺(tái)以注入的方式添加 WeixinJSBridge 這個(gè)對(duì)象。我們可以通過這段代碼看到它暴露的方法: invoke invokeCallbackHandleron publish subscribe subscribe subscribeHandler。
 - 78-235 行:Reporter 對(duì)象,它的作用就是發(fā)送錯(cuò)誤和性能統(tǒng)計(jì)數(shù)據(jù)給后臺(tái)
 - 236-596 行:wx 對(duì)象,頁面的核心之一,一方面封裝 WeixinJSBridge 的 invokeMethod 方位為易于調(diào)用的形式(例如 redirectTo, navigateTo等),另一方面封裝 WeixinJSBridge 回調(diào)方法,調(diào)用者可以使用wx.onAppDataChange(callback) 添加數(shù)據(jù)變更的回調(diào)函數(shù),***提供wx.publishPageEvent 發(fā)送頁面事件到后臺(tái)
 - 607-1267 行:wxparser 對(duì)象,提供 dom 到 wx element 對(duì)象之間的映射操作,提供元素操作管理和事件管理功能
 - 1268-1285 行:轉(zhuǎn)發(fā) window 上的 animation 和 transition 相關(guān)的動(dòng)畫事件到 exparser
 - 1286-1313 行:訂閱并轉(zhuǎn)發(fā) WeixinJSBridge 提供的全局事件到 exparser
 - 1324-1345 行:轉(zhuǎn)發(fā) window 上的 error 以及各種表單事件到 exparser
 - 1347-3744 行:使用 exparser.registerBehavior 和exparser.registerElement 方法注冊(cè)各種以 wx- 做為標(biāo)簽開頭的元素到 exparser
 - 3744-4498 行:virtual dom 渲染算法實(shí)現(xiàn),提供 diff apply render 等方法,該模塊接口基本與 virtual-dom 一致,這里特別的地方在于它所 diff 和生成的并不是原生 DOM,而是各種模擬了 DOM 接口的 wx element 對(duì)象
 - 4599-4510 行:插入默認(rèn)樣式到頁面
 
從頁面 data 到 dom 的主要流程如下:
- var vtree
 - var rootNode
 - document.addEventListener("generateFuncReady", function(e) {
 - var generateFunc = e.detail.generateFunc;
 - wx.onAppDataChange(function(obj) {
 - // 合并 data 到現(xiàn)有 data
 - DataStore.setData(obj.data)
 - // 生成 virtual dom 的 javascript plain object
 - var props = generateFunc(DataStore.getData())
 - // ***次渲染
 - if (obj.options.firstRender) {
 - vtree = createVirtualTree(props, true)
 - rootNode = vtree.render()
 - rootNode.replaceDocumentElement(document.body)
 - wx.initReady()
 - } else {
 - var other_vtree = createVirtualTree(props, false)
 - var patches = vtree.diff(other_vtree)
 - patches.apply(rootNode)
 - vtree = other_vtree
 - document.dispatchEvent(new CustomEvent("pageReRender", {}));
 - }
 - })
 - })
 
上面的 DataStore 對(duì)象提供合并和獲取當(dāng)前頁面 data 對(duì)象的功能,其實(shí)現(xiàn)如下:
- var DataStore = (function() {
 - var data = {}
 - return {
 - getData: function() {
 - return data
 - },
 - setData: function(e) {
 - for (var t in e) {
 - for (var n = (0, parsePath)(t), o = data, a = void 0, s = void 0, c = 0; c < n.length; c++) Number(n[c]) === n[c] && Number(n[c]) % 1 === 0 ? Array.isArray(o) || (a[s] = [], o = a[s]) : "[object Object]" !== Object.prototype.toString.call(o) && (a[s] = {}, o = a[s]), s = n[c], a = o, o = o[n[c]];
 - a && (a[s] = e[t])
 - }
 - }
 - }
 - })()
 - // 解析 key 為 data 內(nèi)對(duì)象的路徑字符串
 - function parsePath(e) {
 - for (var t = e.length, n = [], i = "", r = 0, o = !1, a = !1, s = 0; s < t; s++) {
 - var c = e[s];
 - if ("\\" === c) s + 1 < t && ("." === e[s + 1] || "[" === e[s + 1] || "]" === e[s + 1]) ? (i += e[s + 1], s++) : i += "\\";
 - else if ("." === c) i && (n.push(i), i = "");
 - else if ("[" === c) {
 - if (i && (n.push(i), i = ""), 0 === n.length) throw new Error("path can not start with []: " + e);
 - a = !0, o = !1
 - } else if ("]" === c) {
 - if (!o) throw new Error("must have number in []: " + e);
 - a = !1, n.push(r), r = 0
 - } else if (a) {
 - if (c < "0" || c > "9") throw new Error("only number 0-9 could inside []: " + e);
 - o = !0, r = 10 * r + c.charCodeAt(0) - 48
 - } else i += c
 - }
 - if (i && n.push(i), 0 === n.length) throw new Error("path can not be empty");
 - return n
 - }
 
可以看到,每次 data 變化之后,小程序就會(huì)開始整個(gè)頁面的 diff patch 過程。
對(duì)于原生實(shí)現(xiàn)的組件, exparser 會(huì)在監(jiān)視到數(shù)據(jù)變化后發(fā)送對(duì)應(yīng)事件到 WeixinJSBridge。
service 頁面詳解
service 頁面會(huì)被被拼接為以下的樣子:
- <!DOCTYPE html>
 - <html>
 - <head>
 - <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 - <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
 - <script>
 - var __wxAppData = {}
 - var __wxRoute
 - var __wxRouteBegin
 - </script>
 - <script>var __wxConfig = {"pages":["page/index"],
 - // app 相關(guān)各種配置
 - }</script>
 - <script src="http://70475629.appservice.open.weixin.qq.com/asdebug.js"></script>
 - <script src="http://70475629.appservice.open.weixin.qq.com/WAService.js"></script>
 - <script src="http://70475629.appservice.open.weixin.qq.com/app.js"></script>
 - <script>
 - __wxRoute = 'page/index';
 - __wxRouteBegin = true
 - </script>
 - <script src="http://70475629.appservice.open.weixin.qq.com/page/index.js"></script>
 - </head>
 - <body>
 - <script>
 - window._____sendMsgToNW({
 - sdkName: 'APP_SERVICE_COMPLETE'
 - })
 - </script>
 - </body>
 - </html>
 
除了配置和開發(fā)者編寫的頁面、app.js,頁面還在加載了 asdebug.js 和 WAService.js 兩個(gè)文件。
asdebug.js 文件位于 nwjs 項(xiàng)目目錄下,路徑為app/dist/weapp/appservice/asdebug.js。 它包含了兩個(gè)部分,一個(gè)是 WeixinJSBridge 針對(duì) service 模塊的實(shí)現(xiàn),另一塊是一些方便命令使用的接口, 例如:help() 會(huì)告訴你一些可用的函數(shù):
該文件只會(huì)在開發(fā)者工具內(nèi)被引入,如果小程序在微信內(nèi)運(yùn)行,應(yīng)該會(huì)由微信底層提供 WeixinJSBridge。
WAService 負(fù)責(zé) service 模塊的一些核心邏輯,它包含以下部分 (行號(hào)為 jsbeautify 之后代碼行號(hào),開發(fā)者工具版本:092300):
- 1-78 行: 跟 WAWebview.js 一樣的 WeixinJSBridge 兼容模塊
 - 79-245 行: 跟 WAWebview.js 一樣的 Reporter 模塊
 - 246-1664 行:比 WAWebview.js 中 wx 功能更為豐富 wx 接口模塊
 - 1665-2304 行:appServiceEngine 模塊,提供 Page,App,GetApp 接口
 - 2305-2360 行: 為 window 對(duì)象添加 AMD 接口 require define
 
現(xiàn)在的 WAService 還有有很多地方依賴 window 對(duì)象,所以很有可能它在微信中和開發(fā)者工具內(nèi)一樣,依然運(yùn)行于 webview 標(biāo)簽之內(nèi)。
















 
 
 











 
 
 
 