小程序模板网

从小程序不支持DOM操作开始深入分析小程序运行机制

2020-05-13 荒白米

小程序现在如日中天,各大公司都推出了自己的小程序平台,目前看来运行机制都差不多,数据形成视图,渲染和逻辑分成两个线程,交互通过线程通信实现。

刚开始接触小程序开发的时候,看到小程序的语法觉得很奇怪。看着像react和vue的结合体,疑惑为什么要这么费力的实现这么一套机制。难道是为了体现技术nb? 用了一会就发现问题了,照搬pc开发的那套思想,特么小程序里不支持dom相关的api,很不方便。翻了很多遍微信和支付宝小程序的官方文档,终于有了一点理解。

本文大部分是官方文档引用加上自己一点总结。

web开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应。开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。 而在小程序中,二者是分开的,分别运行在不同的线程中,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。

由于支付宝官方文档说明过于简略,本文结合了微信和支付宝小程序的特点总结,结合如有差异,欢迎指正。

这部分在 微信 和支付宝小程序官方文档都有说明

浏览器环境中渲染线程和js线程是互斥的

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和GUI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

小程序页面文件结构

小程序分为 app 和 page 两层。app 用来描述整个应用,page 用来描述各个页面。

app 由三个文件组成,必须放在项目的根目录。

文件 必需 作用
app.js 小程序逻辑
app.json 小程序全局设置
app.acss 小程序全局样式表

page 由四个文件组成,分别是:

文件 必需 作用
app.js 页面逻辑
app.axml 页面结构
app.json 页面配置
app.acss 页面样式

为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。

单逻辑线程与多webview渲染线程

小程序的逻辑层和渲染层是分开的两个线程,小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS脚本工作在逻辑层。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。

小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由客户端做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。

从逻辑组成来说,一个小程序是由多个“页面”组成的“程序”。宿主环境提供了 App() 构造器用来注册一个程序App,需要留意的是App() 构造器必须写在项目根目录的app.js里,App实例是单例对象,在其他JS脚本中可以使用宿主环境提供的 getApp() 来获取程序实例。

小程序开发框架的逻辑层使用 JavaScript 引擎为小程序提供开发者 JavaScript 代码的运行环境以及微信小程序的特有功能。 逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。 开发者写的所有代码最终将会打包成一份 JavaScript 文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 ServiceWorker,所以逻辑层也称之为 App Service。

const app = getApp(); 
复制代码

参考资料: 微信1 、 微信2 、 微信3

小程序的运行环境

js运行引擎

小程序的 JavaScript 代码分为逻辑层脚本和 sjs/wxs 脚本

支付宝文档 说sjs和逻辑层运行在相同的 JavaScript 引擎的不同线程中。

微信文档 又表示wxs是运行在webview中的,并且提供了更为强大的功能:如果在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍,在 android 设备上二者运行效率无差异;减少通信的次数,让事件在视图层(Webview)响应;用来响应小程序事件,目前只能响应内置组件的事件,不支持自定义组件事件;还能调用逻辑层的事件;

对两者sjs/wxs差别表示吃瓜状态,支付宝的sjs明显功能弱了很多,毕竟鸡肋,一般用来模拟vue里的compued功能使用,无法作为事件回调。微信的wxs提供了更强大的支持。

试了下sjs不带类似computed缓存功能

两个平台都会对新预发的代码进行 babel 转换,使 JavaScript 引擎支持绝大多数 ES6 的新特性,但是对于内置对象未提供完全的Polyfill,具体支持情况可以查阅 支付宝 、 微信

  • 在 iOS 上,小程序逻辑层的 javascript 代码运行在 JavaScriptCore 中;
  • 在 Android 上,小程序逻辑层的 javascript 代码运行在 V8 中;

视图层渲染

  • 在 iOS 上,视图层是由 WKWebView 来渲染的
  • 在 Android 上,微信是由自研 XWeb 引擎基于 Mobile Chrome 内核来渲染的,支付宝未找到说明,猜测是UC

小程序渲染native组件原理

小程序中,有一些组件其实是调用原生组件的,如map、video等,这些复杂交互的控件,原生能带来更好的性能与原生体验。

简单说就是在期望插入原生控件的位置渲染一个HTML元素,拿到此DOM的位置,客户端在相同的位置上,根据宽高插入一块原生区域,位置或宽高发生变化时,组件会通知客户端做相应的调整。 可以直接看 官方文档

为什么这么设计

微信小程序的官方介绍 很全面了

回到标题的问题。因为逻辑层Service中的代码与WebView中的代码完全隔离,JavaScriptCore中并没有document,window等对象(ECMAScript标准没有规定DOM,这其实是浏览器提供的)。js和视图(dom所在)没有运行在同一容器中。

小程序开发可做的优化

减少包的大小

很好理解,小程序初次启动时,客户端需要从 CDN 下载小程序资源包,此后,如果小程序代码包未更新且还被保留在缓存中,则下载小程序代码包的步骤会被跳过。可以做的:

  • 减少在代码包中直接嵌入的资源文件,建议从 CDN 渠道上传
  • 清理无用代码和结构
  • 提升首屏,可以使用分包

减少setData次数,合并setData

每一次setData都是线程通信

支付宝小程序提供了$batchedUpdates

this.$batchedUpdates(() => {
    this.setData({
      counter: this.data.counter + 1,
    });
    this.setData({
      counter: this.data.counter + 1,
    });
  });
复制代码

优化setData

setData是线程通信传递数据,传输时数据需要序列化,框架提供了指定路径设置数据的方便,避免一次传输完整数据。

this.setData({
  'array[0]': 1,
  'obj.x':2,
});
复制代码

针对长列表, 支付宝小程序 提供了优化方法$spliceData,使用方式对应js数组的splice

this.$spliceData({ 'a.b': [1, 0, 5, 6] })
复制代码

针对长列表做优化,避免每次传递整个列表,只会从对应组件节点开始做差异比较

使用 wxs /sjs

小程序中事件响应也需要通过线程通信,如果频繁的触发可能会造成卡顿。例如页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动。一次 touchmove 事件的响应过程为:

a、touchmove 事件从视图层(Webview)抛到逻辑层(App Service)

b、逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置

微信小程序里可以使用wxs响应事件优化,wxs是运行在webview中的,不需要跨线程通信。WXS 函数的除了纯逻辑的运算,还可以通过封装好的ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 足够了:

const wxsFunction = function(event, ownerInstance) {
    const instance = ownerInstance.selectComponent('.classSelector') // 返回组件的实例
    instance.setStyle({
        "font-size": "14px" // 支持rpx
    })
    instance.getDataset()
    instance.setClass(className)
    // ...
    return false // 不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault
}
复制代码

一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟。 官方性能优化文档: 微信 、 支付宝 ;


易优小程序(企业版)+灵活api+前后代码开源 码云仓库:starfork
本文地址:https://www.eyoucms.com/wxmini/doc/course/25125.html 复制链接 如需定制请联系易优客服咨询:800182392 点击咨询
QQ在线咨询