本文探讨一下小程序的 view 模块和 service 模块是如何构成的。
打开微信 web 开发者工具,然后输入 openVendor() 便会打开 WeappVendor这个目录,这里包含了 view 模块和 service 模块使用的几个核心文件:
· wcc 可执行程序,用于将 wxml 转为 view 模块使用的 js 代码,使用方式为wcc xxx.wxml
· wcsc 可执行程序,用于将 wxss 转为 view 模块使用的 css 代码,使用方式为 wcsc xxx.wxss
· WAService.js 提供 service 模块大部分功能,下面会有详细介绍
· WAWebview.js 提供 view 模块大部分功能,下面会有详细介绍
view 页面详解
view 页面的 template 如下:
1. <!DOCTYPE html>
2. <html lang="zh-CN">
3. <head>
4. <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
5. <meta charset="UTF-8" />
6. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
7.
8. <script>
9. var __webviewId__;
10. </script>
11.
12. <!-- percodes -->
13.
14. <!--{{WAWebview}}-->
15.
16. <!--{{reportSDK}}-->
17.
18. <!--{{webviewSDK}}-->
19.
20. <!--{{exparser}}-->
21.
22. <!--{{components_js}}-->
23.
24. <!--{{virtual_dom}}-->
25.
26. <!--{{components_css}}-->
27.
28. <!--{{allWXML}}-->
29.
30. <!--{{eruda}}-->
31.
32. <!--{{style}}-->
33.
34. <!--{{currentstyle}}-->
35.
36. <!--{{generateFunc}}-->
37. </head>
38.
39. <body>
40. <div></div>
41. </body>
42.
43. </html>
其中 <!-- percodes --> 会在 dev 模式开启后被替换为一个时间锚点,例如:
1. <script>var pageFrameStartTime = new Date();</script>
<!--{{WAWebview}}--> 会被 WAWebview.js 内代码替换
<!--{{WAWebview}}--> 到 <!--{{generateFunc}}--> 之间暂时没有被使用到
<!--{{generateFunc}}--> 会被 wcc 命令生成后的 js 代码替换
除了上面这些,页面上还会被插入页面和应用的 style 标签,如:
1. <link rel="stylesheet" type="text/css" href="index.wxss">
这里的 wxss 文件包含的是原始 wxss 文件转换后的 css
以及生成 DOM 的启动脚本:
1. <script>
2. document.dispatchEvent(new CustomEvent("generateFuncReady", {
3. detail: {
4. generateFunc: $gwx('./page/index.wxml')
5. }
6. }))
7. </script>
WAWebview.js 文件中的各个模块(行号为 jsbeautify 之后代码行号,开发者工具版本:092300):
· 1-77 行: WeixinJSBridge 对象兼容层,这个大概只会在调试时用到,因为开发时和运行时页面都会被后台以注入的方式添加 WeixinJSBridge 这个对象。我们可以通过这段代码看到它暴露的方法: invoke invokeCallbackHandleron publish subscribe subscribe subscribeHandler。
· 78-235 行:Reporter 对象,它的作用就是发送错误和性能统计数据给后台
· 236-596 行:wx 对象,页面的核心之一,一方面封装 WeixinJSBridge 的 invokeMethod 方位为易于调用的形式(例如 redirectTo, navigateTo等),另一方面封装 WeixinJSBridge 回调方法,调用者可以使用wx.onAppDataChange(callback) 添加数据变更的回调函数,最后提供wx.publishPageEvent 发送页面事件到后台
· 607-1267 行:wxparser 对象,提供 dom 到 wx element 对象之间的映射操作,提供元素操作管理和事件管理功能
· 1268-1285 行:转发 window 上的 animation 和 transition 相关的动画事件到 exparser
· 1286-1313 行:订阅并转发 WeixinJSBridge 提供的全局事件到 exparser
· 1324-1345 行:转发 window 上的 error 以及各种表单事件到 exparser
· 1347-3744 行:使用 exparser.registerBehavior 和exparser.registerElement 方法注册各种以 wx- 做为标签开头的元素到 exparser
· 3744-4498 行:virtual dom 渲染算法实现,提供 diff apply render 等方法,该模块接口基本与 virtual-dom 一致,这里特别的地方在于它所 diff 和生成的并不是原生 DOM,而是各种模拟了 DOM 接口的 wx element 对象
· 4599-4510 行:插入默认样式到页面
从页面 data 到 dom 的主要流程如下:
1. var vtree
2. var rootNode
3.
4. document.addEventListener("generateFuncReady", function(e) {
5. var generateFunc = e.detail.generateFunc;
6. wx.onAppDataChange(function(obj) {
7. // 合并 data 到现有 data
8. DataStore.setData(obj.data)
9. // 生成 virtual dom 的 javascript plain object
10. var props = generateFunc(DataStore.getData())
11.
12. // 第一次渲染
13. if (obj.options.firstRender) {
14. vtree = createVirtualTree(props, true)
15. rootNode = vtree.render()
16. rootNode.replaceDocumentElement(document.body)
17. wx.initReady()
18. } else {
19. var other_vtree = createVirtualTree(props, false)
20. var patches = vtree.diff(other_vtree)
21. patches.apply(rootNode)
22. vtree = other_vtree
23. document.dispatchEvent(new CustomEvent("pageReRender", {}));
24. }
25. })
26. })
上面的 DataStore 对象提供合并和获取当前页面 data 对象的功能,其实现如下:
1. var DataStore = (function() {
2. var data = {}
3. return {
4. getData: function() {
5. return data
6. },
7. setData: function(e) {
8. for (var t in e) {
9. 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]];
10. a && (a[s] = e[t])
11. }
12. }
13. }
14. })()
15.
16. // 解析 key 为 data 内对象的路径字符串
17. function parsePath(e) {
18. for (var t = e.length, n = [], i = "", r = 0, o = !1, a = !1, s = 0; s < t; s++) {
19. var c = e[s];
20. if ("\\" === c) s + 1 < t && ("." === e[s + 1] || "[" === e[s + 1] || "]" === e[s + 1]) ? (i += e[s + 1], s++) : i += "\\";
21. else if ("." === c) i && (n.push(i), i = "");
22. else if ("[" === c) {
23. if (i && (n.push(i), i = ""), 0 === n.length) throw new Error("path can not start with []: " + e);
24. a = !0, o = !1
25. } else if ("]" === c) {
26. if (!o) throw new Error("must have number in []: " + e);
27. a = !1, n.push(r), r = 0
28. } else if (a) {
29. if (c < "0" || c > "9") throw new Error("only number 0-9 could inside []: " + e);
30. o = !0, r = 10 * r + c.charCodeAt(0) - 48
31. } else i += c
32. }
33. if (i && n.push(i), 0 === n.length) throw new Error("path can not be empty");
34. return n
35. }
可以看到,每次 data 变化之后,小程序就会开始整个页面的 diff patch 过程。
对于原生实现的组件, exparser 会在监视到数据变化后发送对应事件到 WeixinJSBridge。
service 页面详解
service 页面会被被拼接为以下的样子:
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5. <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
6. <script>
7. var __wxAppData = {}
8. var __wxRoute
9. var __wxRouteBegin
10. </script>
11. <script>var __wxConfig = {"pages":["page/index"],
12. // app 相关各种配置
13. }</script>
14. <script src="http://70475629.appservice.open.weixin.qq.com/asdebug.js"></script>
15. <script src="http://70475629.appservice.open.weixin.qq.com/WAService.js"></script>
16. <script src="http://70475629.appservice.open.weixin.qq.com/app.js"></script>
17. <script>
18. __wxRoute = 'page/index';
19. __wxRouteBegin = true
20. </script>
21. <script src="http://70475629.appservice.open.weixin.qq.com/page/index.js"></script>
22. </head>
23.
24. <body>
25. <script>
26. window._____sendMsgToNW({
27. sdkName: 'APP_SERVICE_COMPLETE'
28. })
29. </script>
30. </body>
31.
32. </html>
除了配置和开发者编写的页面、app.js,页面还在加载了 asdebug.js 和 WAService.js 两个文件。
asdebug.js 文件位于 nwjs 项目目录下,路径为app/dist/weapp/appservice/asdebug.js。 它包含了两个部分,一个是 WeixinJSBridge 针对 service 模块的实现,另一块是一些方便命令使用的接口, 例如:help() 会告诉你一些可用的函数:
该文件只会在开发者工具内被引入,如果小程序在微信内运行,应该会由微信底层提供 WeixinJSBridge。
WAService 负责 service 模块的一些核心逻辑,它包含以下部分 (行号为 jsbeautify 之后代码行号,开发者工具版本: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 对象添加 AMD 接口 require define
现在的 WAService 还有有很多地方依赖 window 对象,所以很有可能它在微信中和开发者工具内一样,依然运行于 webview 标签之内。
登录 | 立即注册