微信小程序架构分析 (中)

【引自第九程序的博客】本文探讨一下小程序的 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 

可以看到,每次 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 标签之内。

作者:第九程序

来源:51CTO

时间: 2024-09-10 17:59:37

微信小程序架构分析 (中)的相关文章

微信小程序架构分析 (上)

[引自第九程序的博客]相信不少上手试用了微信小程序开发者工具的开发者都会对其实现有些疑惑, 本文试图对其架构模型进行一些解析.如有错误之处,欢迎留言指出. 本文分为以下几个部分: 小程序调试技巧 小程序主要模块构成 小程序模块间通信 设计理念分析 小程序调试技巧 微信开发者工具默认禁用了右键打开调试面板功能,我们可以修改开发者工具部分代码移除该限制. 找到 app.nw 项目根目录,Mac 下为/Applications/wechatwebdevtools.app/Contents/Resour

微信小程序架构分析 (下)

[引自第九程序的博客]这一篇拖了一段时间,原因是实现一个可以运行微信小程序的 web 环境比我想象中要困难一些, 这一方面是因为微信对于代码进行了压缩混淆,另一方面主要原因是开发者工具内部逻辑调用比较复杂(难怪 bug 不少),完全无法拿出来重用. 小程序实时运行工具 wept 的开发已经基本完成了, 你可以通过我的代码对小程序的 web 环境实现有更全面的认识.下面我将介绍它的实现过程以及实时更新的原理. 小程序 web 服务实现 我在 wept 的开发中使用 koa 提供 web 服务,以及

移动开发之微信小程序——资料集合

本文转载自:知乎 有需要下载的客官可可以点击知乎去下载相关资料 一:官方地址集合:1:官方工具:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=14764346784612:简易教程:https://mp.weixin.qq.com/debug/wxadoc/dev/?t=14764346775993:设计指南:https://mp.weixin.qq.com/debug/wxadoc/design/index

史诗手册!微信小程序新手自学入门宝典!你想要的都在这里

一.小程序官方指南 1:官方开发工具下载: https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=201714 0.12.1304Win版:https://pan.baidu.com/s/1miNleBY 0.12.1304Mac版:https://pan.baidu.com/s/1qYNIQZy 2:官方提供的简单教程 https://mp.weixin.qq.com/debug/wxadoc/dev/ 3:小程序

【转】微信小程序给程序员带来的可能是一个赚钱的机遇

自上周被微信小程序刷屏之后,这周大家都在谈微信小程序能够带来哪些红利的话题,其实我想从程序员的角度来谈谈,带给我们程序员来的红利,或许是我们程序员创业或者赚钱的机遇. 其实我从<作为移动开发程序员,你是否患有微信应用号"恐惧症"?>文章中已经说过了,微信小程序可能是原生的机遇,在程序员面对微信小程序的恐惧中说其实也是我们程序员创业的春天或者挣外快的一个机遇. 我们想想,像微信服务号是面对企业的一种宣传,对企业有利,而微信公众号是自媒体人的天下,打开了自媒体人的春天,自媒体时

微信小程序 require机制详解及实例代码_JavaScript

微信小程序 require机制详解 一, JS模块加载:一次性加载全部JS, 但并不一定立即执行. 先提一提微信小程序架构: 类浏览器 -> HTTP本地服务 -> 云端服务 微信小程序运行的架构,基本上是浏览器 -> HTTP本地服务 -> 云端服务, HTTP本地服务用来读取本地文件或者代理云端的文件资源.读取项目中JS文件, 是由HTTP本地服务取本地存储的脚本文件. 似乎比较简单,一个HTML 引用所有JS文件 既然采用了这种架构,那微信小程序就类似浏览器那样,借助一个HT

微信小程序开发(2) 计算器

在这篇微信小程序开发教程中,我们将介绍如何使用微信小程序开发计算器功能. 本文主要分为两个部分,小程序主体部分及计算器业务页面部分   一.小程序主体部分 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:   1. 小程序逻辑 App({ onLaunch: function() { // Do something initial when launch. }, onShow: function() { // Do something when show. }, onHide: f

微信小程序开发(5) 2048游戏

在这篇微信小程序开发教程中,我们将介绍如何使用微信小程序开发2048小游戏. 本文主要分为两个部分,小程序主体部分及小游戏页面部分   一.小程序主体部分 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:   1. 小程序逻辑 App({ onLaunch: function() { // Do something initial when launch. }, onShow: function() { // Do something when show. }, onHide: f

微信小程序开发(4) 企业展示

在这篇微信小程序开发教程中,我们将介绍如何使用微信小程序开发企业内部宣传展示等功能.   一.小程序主体部分 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:   1. 小程序逻辑 App({ onLaunch: function() { // Do something initial when launch. }, onShow: function() { // Do something when show. }, onHide: function() { // Do somet