【小程序 - 大智慧】深入微信小程序的核心原理


目录


课程目标

本次课程主要通过后台管理小程序回顾一下小程序的高阶语法,然后讲解整体小程序流程原理:

  • 前端相关的编码规范、设计规范
  • 页面切换、生命周期、数据通信等基础知识
  • 双线程架构、webview 渲染、PageFrame 模板、通讯系统、生命周期、跨端框架等
  • 掘金小册推荐 https://juejin.cn/book/6982013809212784676

背景

https://zh.wikipedia.org/zh-cn/微信小程序

微信小程序于 2017 年上线,当时企业普遍把 H5 作为公众号的引流入口,公众号正火,但是当时由于 H5 不安全、性能不好、占用存储空间大的问题,微信需要设计一个全新的系统进行迭代。

双线程架构

面试官:说说什么是进程?什么是线程?区别? | web前端面试 - 面试官系列

进程:操作系统进行进行资源分配和调度的基本单位

线程:操作系统能够进行运算调度的最小单位,其是进程中的一个执行任务(控制单元),负责当前进程中程序的执行

微信小程序的设计和前端浏览器是不一致的,它隔离了 JS 操作层 和 界面渲染层,前端是互通的,可以通过 window document 任意的调用 dom 元素和执行脚本,但是小程序作为微信的工具,势必不允许像 web 端这样自由的操作空间,所以:

  1. App Service(逻辑层):单纯执行 JavaScript 的沙箱环境,无法操作 Dom 和访问浏览器 Api ,只能借助 Native 异步查询 DOM 元素并操作,小程序的 WXML 和 WXSS 也会被编译为 JS 脚本进行注入操作。由于小程序内部只有一个 App 实例,通过这个 App 主线程 进行调度
  2. Web View(渲染层):单纯页面的展示,模拟 app 多页面模式,具有多个 webview ,可以暂时抽象成 浏览器的 tab 页面(并非 VUE 的单页面结构,像 App 多页面结构)
  3. Native(微信客户端):利用微信缓存机制,提前注入 微信 SDK、接口请求、组件渲染(wx.request、wx.showToast 都是存放在这里)

WebView 结构

  1. 第一阶段是启动一个 WebView ,在iOS和 Android 系统上,操作系统启动WebView都需要一小段时间。
  2. 第二阶段是在 WebView 中初始化基础库,此时还会进行一些基础库内部优化,以提升页面渲染性能。
  3. 第三阶段是注入小程序 WXML 结构和 WXSS 样式,使小程序能在接收到页面初始数据之后马上开始渲染页面(这一阶段无法在小程序启动前执行)。
  4. 每一个页面都独立运行在一个页面层级上。小程序启动时仅有一个页面层级,每次调用 wx.navigateTo,都会创建一个新的页面层级,wx.navigateBack 会销毁一个页面层级。
  1. 微信开发者工具底层是 Chrome XWeb 内核,进行开发跨平台的桌面应用(如QQ、WeChat客户端)。
  2. 左上角选择,微信开发者工具 -> 调试 -> 调试微信开发者工具,和谷歌调试界面几乎一模一样(利用这个工具,我们后续可以处理一些内部报错的调试BUG)。
  3. 接下来,我们利用脚本遍历开发工具的 webview 元素。
javascript 复制代码
// 获取所有的 webview
document.getElementsByTagName('webview')

// 打开调试的 webview
document.getElementsByTagName('webview')[0].showDevTools(true, null)
  1. webview 的含义如下:当前小程序的页面 两个渲染层 逻辑层 调试器
  2. 新开一个页面就会新增一个 webview,微信限制最多10个以保证性能问题的底层原因
  3. 利用命令打开一个 webview 页面进行具体的调试
  4. 页面的样式、调试库、渲染库、开发工具的配置...
  5. body 下面的标签就是利用 小程序开发的 Exparser 框架将 dom 转换为自定义组件的一套规则
  6. 每个 webview 都加载了这么多文件,是如何保证高效运作的?

快速渲染 PageFrame

  1. 利用 page frame 模板生成 webview 视图。
  2. 基本的 webview 模板和之前打开的一致,但是包含一些注释占位符,后续会被编译为具体的执行 js 文件。

加载流程如下:

  1. 首页启动时,即第一次通过 pageframe.html 生成内容后,后台服务会缓存 pageframe.html 模板首次生成的html内容。
    1. 非首次新打开页面时,页面请求的 pageframe.html 内容直接走后台缓存。
    2. 非首次新打开页面时,pageframe.html 页面引入的外链js资源(如上图所示)走本地缓存。
  2. 生成 webview 模板后,初始化 webview 地址 http://127.0.0.1:${global.proxyPort}/aboutblank?${c} 空地址,其中 ${c} 为对应 webview 的 id。
  3. 监听页面的 ready 操作,注入执行代码脚本生成最终的 webview 地址和执行代码。

编译原理

基础语法不作过多赘述,主要编译原理和rpx动态适配。WXSS/WXML并不可以直接执行在webview层进行渲染,通过wcsc/wcc执行脚本编译为js文件,然后注入webview中,这里可以演示一下。

javascript 复制代码
// help() 查看编译命令
help()

// 编译 WXSS 命令
./wcsc -js index.wxss >> style.js

  // 编译 WXML 命令
./wcc -js index.wxml >> test.js

// 执行 $gwx 文件
var decodeName = decodeURI("./pages/home-tab/index.wxml")
var generateFunc = $gwx(decodeName)

generateFunc()
javascript 复制代码
var eps = 1e-4
var transformRPX =
  window.__transformRpx__ ||
  function (number, newDeviceWidth) {
    if (number === 0) return 0
    number = (number / BASE_DEVICE_WIDTH) * (newDeviceWidth || deviceWidth)
    number = Math.floor(number + eps)
    if (number === 0) {
      if (deviceDPR === 1 || !isIOS) {
        return 1
      } else {
        return 0.5
      }
    }
    return number
  }
  1. wcsc 将 WXSS 编译为 JS 文件。

  2. JS 文件注入到 WebView 中。

  3. 逻辑层执行 JS 文件,主要是设备信息获取(宽度、高度、横屏)、特定规则 rpx 适配为 px,写入编译后的 CSS 文件到 head style 头部。

  4. WCC 将 WXML 编译为 JS 文件。

  5. 但是文件作了压缩混淆,本质逻辑执行 $gwx 函数。

  6. generateFunc 就是接受动态数据,并生成虚拟 DOM 树的函数,DOM 树已存在的数据直接渲染为 wx-view/wx-text,需要 JS 脚本异步获取的数据采取 tag: virtual 虚拟 DOM 元素进行占位,等到获取后端数据之后直接填充 => 真实 DOM。

Exparser

WebComponents Shadow Dom

Exparser是微信小程序的组件组织框架,内置在小程序基础库中,为小程序提供各种各样的组件支撑。内置组件和自定义组件都有Exparser组织管理。

这部分后面单独抽离一篇文章进行讲解...

通讯系统

在正式讲解小程序的通讯系统前,先来熟悉一下小程序的 发布 - 订阅模式

美团一面:你了解发布-订阅模式吗?「每天搞透一道JS手写题💪Day8」 - 掘金

概述:引入中间平台进行注册和通知,有效解决了观察者维护列表导致的解耦不彻底问题

观察者通过 onEventBus 注册事件,然后 Subject 通过 emitEventBus 发射事件,由 EventBus 来向观察者更新。本质上是维护一个 events 对象,通过事件名注入到 events ,每个 事件名 里面都有相应的回调函数,发布之后会分发到相应事件 回调函数 的方法。

javascript 复制代码
class PubSub {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (typeof event !== 'string') {
      throw new Error('Event name must be a string');
    }
    if (typeof callback !== 'function') {
      throw new Error('Callback must be a function');
    }
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  unsubscribe(event, callback) {
    if (typeof event !== 'string') {
      throw new Error('Event name must be a string');
    }
    if (typeof callback !== 'function') {
      throw new Error('Callback must be a function');
    }
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(cb => cb !== callback);
  }

  publish(event, data) {
    if (typeof event !== 'string') {
      throw new Error('Event name must be a string');
    }
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(data));
  }
}

在微信小程序执行过程中,Native层(客户端层)分别向渲染层与逻辑层注入 WeixinJSBridge 以达到线程通讯的目的,前面也提到了 webview 脚本注入的原理,WeixinJSBridge 提供了如下几个方法:

  • invoke - 调用 Native API,以api方式调用开发工具提供的基础能力,并提供对应api执行后的回调
  • invokeCallbackHandler - Native 传递 invoke 方法回调结果
  • on - 用来收集小程序开发者工具触发的事件回调
  • publish - 渲染层发布消息,用来向逻辑业务层发送消息,也就是说要调用逻辑层的事件方法
  • subscribe - 订阅逻辑层消息
  • subscribeHandler - 视图层和逻辑层消息订阅转发
  • setCustomPublishHandler - 自定义消息转发
  1. WXML 渲染为虚拟 DOM,添加 addEventListener 进行事件解析。
  2. 逻辑层解析的时候会利用 JSBridge subscribe 订阅事件名称到渲染层,执行逻辑还在逻辑层,只是把名称和具体代码在渲染层作了映射。
  3. 触发事件,渲染层发布消息 publish 携带方法名经过 JSBridge 到达逻辑层。
  4. 逻辑层执行将 Data 以 JSON 字符串格式的数据经过 JSBridge 渲染给 WXML。

生命周期

  1. onLaunch(App onLaunch) App 主应用开始初始化,逻辑线程开始执行。
  2. onLoad(Object query) 页面加载时触发,一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数。
  3. onShow() 页面显示/切入前台时触发。
  4. onHide() 页面隐藏/切入后台时触发。如 wx.navigateTo 或底部 tab 切换到其他页面,小程序切入后台等。
  5. onReady() 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
  6. onUnload() 页面卸载时触发。如 wx.redirectTowx.navigateBack 到其他页面时。
  • wx.navigateTo 方式是创建新的 webview(添加新的页面栈,新加入的页面从头渲染,之前的页面 onHide 隐藏,为了使用 navigateBack 走缓存 onShow)。
  • wx.redirectTo 以及 wx.navigateBack 是通过更新自身 webview 进行页面转换的,所以当前页面会进行卸载操作,并且重新生成新页面。

基础库解包

微信开发者工具里面选择的基础库为微信中的基础库版本(后面标识了兼容性和用户的数量),可用于微信开发者工具内的调试。每个版本的微信客户端都会自带一个版本的小程序基础库,而不是微信客户端带着所有版本的基础库,所以开发时选择的版本尽量和客户端的一致(最新的为好),避免 API 兼容性问题。

接下来,我们利用 openVendor() 找到 基础库进行解析,找到原始的 wxvpkg 然后利用 Github 工具进行反编译出源码,https://github.com/csj5588/wxappUnpacker

构建后的结果在 dist 文件下,本质上还是渲染层和逻辑层两个线程脚本之间的交互流程,后续在深入探讨。

跨端框架

由于小程序语法较为原始,工程化工具不支持,市面上太多平台的小程序需要适配开发,基于此市面上的第三方框架也慢慢进入大众的视野,目前来说流行的就是 taro 和 uni-app。

下面先来介绍下框架的分类:

预编译

本质上是利用 DSL(语法规则) + 语法解析,将一些逻辑转换为小程序支持的语法,但是这样存在一些问题:

  • 如果React或者Vue后期再出一些新特性的话,预编译框架都需要进行语法解析扩展编写。
  • 兼容问题,比如小程序不支持的一些属性,如果不支持,预编译框架要进行兼容。

半编译半运行

基本上运行时的框架都是参考Vue的框架才可以达到运行时的目的,注意小程序是不支持直接操作DOM元素的,所以只需要将Vue的patch挂载真实DOM流程修改为小程序setData的逻辑,template转换为WXML,style适配下规则就可以。

mpvuegithub地址请参见)是一个使用Vue.js开发小程序的前端框架。框架基于Vue.js核心,mpvue修改了Vue.js的runtimecompiler实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套Vue.js开发体验。

下面我们来看一下源码是如何实现的?

  1. 修改了 vue 的 patch 阶段进行 this.$updateDataToMP()方法。
  2. this.$updateDataToMP()方法就进行了setData的一个调用,还进行了diffData数据的比对和throttle函数优化双线程通讯的性能。

运行时框架

运行时框架主要解决需要 template 转换为 WXML 这部分逻辑,这部分可以借助小程序的template模版机制来进行解决。

这样的话我们只需要进行 root 数据的操作即可,操作完成后利用 setData 渲染机制 发送到渲染层进行操作即可,Taro4 我看编译的结果似乎也是按照这样的格式操作的,后续再看看。

主流技术

Taro

Taro 介绍 | Taro 文档

Taro 特点总结

  • 采用 TypeScript (TS) 加上部分 Rust 进行开发
  • 底层标准参考 微信小程序,兼容性较好,其他平台的兼容性未知
  • ReactWebpack 语法有良好的兼容性支持,Vue 3 加上 Vite 新推出,也可以用
  • 调试开发需要下载不同平台的工具,无法直接导出运行
  • GitHub 上搜索近两年 stars 30+ 的开源项目,资源只有一页,显示资源较少
  • 不支持使用 Vue 3scoped CSS 样式隔离语法,这可能需要更详细的介绍

资源链接

Taro UI | O2Team

NutUI - 移动端组件库

组件库选择建议

  • Taro 组件库 :由 Taro 维护,但不推荐安装
  • NutUI 组件库:多端组件库分离,推荐作为开发的首选

uni-app

uni-app官网
相关资源链接

uni-app 特点总结

本质上有两种体系:

  • 一种是 uniapp + HBuilder 工具构建的一套自己的规范,其中包含:uts(TypeScript 超集)、uni-modules (依赖管理)、uvue(编译语言)、uniCloud(云服务),最终这些被 uni-app x 统一集成,需要重新学习一套标准、时间成本和难度过高,编辑器也不如前端常用的编辑器 VSCode、WebStorm
  • 另一种可以借助原本的命令行执行,和其他的前端项目一致,可以借助第三方优质模板,但是注意 app 部分打包仍然需要 HBuilder 开发工具(推荐)

汇总

维度 Taro 得分 Uni-app 得分
社区文档 清晰明确 8 较为混乱、自有体系和前端不兼容 6
上手成本 VUE 版本上手容易、React 较高 8 底层采用 VUE 开发,熟悉程度更高 9
开源项目 pushed:>2023-09-01 stars:>30 68 7 143 10
Github 活跃 stars:35.3k+ issues:1.2k + 1w+ 10 stars:39.9k+ issues:0.9k + 3k+ 8
三方组件库 官方组件库统一,资源优质 NutUI VUE 6k+ 9 官方组件库较为简陋、社区UI生态较差、大部分收费、一部分不敢用 5
TS 支持 兼容性优秀 9 兼容性优秀 8
VUE 语法兼容 兼容大部分 VUE 语法 7 兼容大部分 VUE 语法 8
React 语法兼容 支持 0 不支持 0
总计 🥇🏅🎖️ 58 54

跨端开发框架深度横评

下周安排

开源商城项目拆解

相关推荐
只想摆烂@12 分钟前
C# winfrom 如何多窗体优雅的回调方法
开发语言·c#
西猫雷婶14 分钟前
python画图|中秋到了,尝试画个月亮(球体画法)
开发语言·python
IT枫斗者16 分钟前
集合工具类
java·linux·数据库·windows·算法·microsoft
星迹日16 分钟前
C语言:结构体
c语言·开发语言·经验分享·笔记
朱皮皮呀22 分钟前
排序算法-归并排序
数据结构·算法·排序算法·归并排序
MogulNemenis23 分钟前
力扣100题——贪心算法
算法·leetcode·贪心算法
aWty_25 分钟前
机器学习--线性回归
python·算法·机器学习·线性回归
会敲代码的小张28 分钟前
设计模式-观察者模式
java·开发语言·后端·观察者模式·设计模式·代理模式
宗浩多捞32 分钟前
C++设计模式(更新中)
开发语言·c++·设计模式