# 微信小程序运行原理
微信小程序介于 web 与 原生app 之间的混合app,具备丰富的调用手机各种功能的接口,同时又具备灵活性,跨平台
# 小程序与普通网页开发的区别
网页开发中,渲染线程JS线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,两者可以直接进行通信
小程序的逻辑层和渲染层是分开的,分别运行在不同的线程中。不同直接进行通信
# 小程序目录结
project
├── pages
| ├── index
| | ├── index.json index 页面配置
| | ├── index.js index 页面逻辑
| | ├── index.wxml index 页面结构
| | └── index.wxss index 页面样式表
| └── log
| ├── log.json log 页面配置
| ├── log.wxml log 页面逻辑
| ├── log.js log 页面结构
| └── log.wxss log 页面样式表
├── app.js 小程序逻辑
├── app.json 小程序公共设置
└── app.wxss 小程序公共样式表
# 为什么小程序快
安装包缓存
分包加载
独立渲染线程
Webview 预加载
Native组件
# 小程序启动加载
小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。
热启动:假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动
冷启动:指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。
# 更新机制
小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。
如果需要马上应用最新版本,可以使用 wx.getUpdateManager
API 进行处理。
# 运行机制
小程序没有重启的概念
当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁
当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁
# 小程序架构
微信小程序的框架包含两部分:View视图层(可能存在多个)、App Service逻辑层(一个)
View层用来渲染页面结构, 使用WebView渲染
AppService层用来逻辑处理、数据请求、接口调用,它们在两个线程里运行,逻辑层使用JSCore运行
视图层和逻辑层通过系统层的 WeixinJsBridage
进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理
这张图展示了视图层、逻辑层之间通信方式,以及JsBridge起到纽带的作用。我们可以做以下几点总结:
视图层和逻辑层分开在两个线程中运行;
视图层、逻辑层通过事件完成通信;
JsBridge一方面传递基础功能,另一方面做视图层和逻辑层的数据传递工作;
# 执行环境
微信小程序运行在三端:iOS、Android 和 用于调试的开发者工具
三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的:
在 iOS 上,小程序的 javascript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的,环境有 iOS8、iOS9、iOS10
在 Android 上,小程序的 javascript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 53/57 内核来渲染的
在 开发工具上, 小程序的 javascript 代码是运行在 nwjs 中,是由 Chrome Webview 来渲染的
# 视图层和逻辑层
视图层(不包括Native组件)在webview组件中渲染;逻辑层的js代码则通过js容器来执行。js代码执行过程中可以通过JsBridge调起微信、系统的能力,也可以将数据通过setData()函数传递给视图层做渲染。视图层在渲染和交互过程中可以通过事件与客户端进行通信
视图层向逻辑层的通信由事件来完成,而逻辑层数据向视图层发送渲染指令是通过 Page 的 setData
函数。下面我们详细说明他们的细节原理
这张生命周期图非常详尽的描述了一个页面从创建入栈、数据交互、销毁出栈的整个过程。在页面渲染和使用过程中会出现大量的事件,而这些事件会被JsBridge捕获到,并传递给逻辑层处理,主要包括:生命周期事件、UI事件
生命周期事件
视图进程在完成阶段性工作后,需要向逻辑层同步其当前状态以便逻辑层做出应对策略。主要包括:onLoad
、onReady
、onShow
、onHide
、onUnload
、onPullDownRefresh
、onReachBottom
、onShareAppMessage
等
UI事件
视图层向逻辑层的通信方式
这类事件绑定在组件上,触发则可以将用户的行为反馈到逻辑层对应的注册函数,如 bindtap
、bindinput
、bindconfirm
、bindfocus
、bindsubmit
、bindchange
、bindlinechange
等
Page.prototype.setData()
逻辑层向视图层发送渲染指令的方式
setData
函数用于将数据从逻辑层发送到视图层,同时改变对应的 this.data
的值。
注意:data
将会以 JSON 的形式由逻辑层传至渲染层,所以其数据必须是可以转成 JSON 的格式:字符串,数字,布尔值,对象,数组
从性能角度考虑,UI渲染是比较耗时的,一方面尽量减少页面渲染操作,另一方面其会对 data
的内容做深层次 diff
,尽量控制 data
的大小和数据结构的深度(官方限制data数据量最大为1024K)。
# 小程序基础库
小程序基础库就同 JS-SDK 类似,为当前应用调起手机系统能力和微信能力,两者的不同点在于:
支持的API:小程序基础库对系统能力和微信能力做了更全面的封装,包括网络请求、存储等,而网页版JS-SDK相应的功能直接调用window能力
引入方式:小程序基础库直接集成到微信的不同版本中,JS-SDK是以一个js文件的形式被引入项目里;
# 小程序基础库与微信客户端之间的关系
小程序的能力需要微信客户端来支撑,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端
官方的这种说法存在一些问题。现在基础库版本和客户端版本并不是一一对应关系。客户端可以主动升级小程序基础库版本达到灰度上线新版的目的,所以必然存在一个客户端版本对应多个基础库版本的情况
小程序基础库更新时机
为了避免新版本的基础库给线上小程序带来未知的影响,微信客户端都是携带 上一个稳定版 的基础库发布的。
在新版本客户端发布后,我们再通过后台灰度新版本基础库,灰度时长一般为 12 小时,在灰度结束后,用户设备上才会有新版本的基础库
以微信 6.5.8 为例,客户端在发布时携带的是 1.1.1 基础库(6.5.7上已全量的稳定版)发布,在 6.5.8 发布后,我们再通过后台灰度 1.2.0 基础库。
“细思恐极”,如果我们已经完成一台装有 6.5.4 版本微信的Oppo手机对小程序的兼容测试,很有可能过几天这台Oppo手机将小程序基础库更新到新版本导致小程序不可用。建议:了解产品的用户手机微信版本分布,确定回归覆盖范围,完成回归测试。
# 小程序开发经验
小程序存在的问题
小程序仍然使用WebView渲染,并非原生渲染
需要独立开发,不能在非微信环境运行
开发者不可以扩展新组件
WXSS中无法使用本地(图片、字体等)
WXSS转化成js 而不是css。
WXSS不支持级联选择器
小程序的优点
提前新建 WebView,准备新页面渲染
使用 Virtual DOM,进行局部更新
全部使用
https
,确保传输中安全加入 rpx单位,隔离设备尺寸,方便开发
# 优化建议(官方建议)
setData 工作原理
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,
并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript
所实现。
即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境
而 evaluateJavascript
的执行会受很多方面的影响,数据到达视图层并不是实时的
常见的 setData
操作错误
频繁的去
setData
在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:
Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
每次 setData
都传递大量新数据
由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程
- 当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。
# 图片资源
目前图片资源的主要性能问题在于大图片和长列表图片上,这两种情况都有可能导致 iOS 客户端内存占用上升,从而触发系统回收小程序页面
在 iOS 上,小程序的页面是由多个 WKWebView 组成的,在系统内存紧张时,会回收掉一部分 WKWebView。从过去我们分析的案例来看,大图片和长列表图片的使用会引起 WKWebView 的回收。
# 代码包大小的优化
开发者在实现业务逻辑同时也有必要尽量减少代码包的大小,因为代码包大小直接影响到下载速度,从而影响用户的首次打开体验。除了代码自身的重构优化外,还可以从这两方面着手优化代码大小:
分包加载
对小程序进行分包,可以优化小程序首次启动的下载时间
清理没有使用到的代码和资源
目前小程序打包是会将工程下所有文件都打入代码包内,也就是说,这些没有被实际使用到的库文件和资源也会被打入到代码包里,从而影响到整体代码包的大小。
# 预先加载数据
原理
小程序在启动时,会直接加载所有页面逻辑代码进内存,即便 page2 可能都不会被使用。在 page1 跳转至 page2 时,page1 的逻辑代码 Javascript 数据也不会从内存中消失。 page2 甚至可以直接访问 page1 中的数据
小程序的这种机制差异正好可以更好的实现预加载。通常情况下,我们习惯将数据拉取写在 onLoad 事件中。但是小程序的 page1 跳转到 page2,到 page2 的 onLoad 是存在一个 300ms ~ 400ms 的延时的。如下图:
在pageA
跳到pageB
的时候,在pageB
使用onNavigate
生命周期来在页面B即将被加载时运行
微信小程序运行流程看这篇就够了 (opens new window)
深入解读-微信小程序SDK (opens new window)
浅谈小程序运行机制 (opens new window)
uni-app →