# 小程序

# 小程序的运行环境

​网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。

运行环境 逻辑层 渲染层
iOS JavaScriptCore WKWebView
安卓 V8 chromium定制内核
小程序开发者工具 NWJS Chrome WebView

同时,每个小程序页面都是用不同的WebView去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。

# WXML 模板

WXML 提供两种文件引用方式import和include:

  • import 具有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件中 import 的 template,简言之就是 import 不具有递归的特性。
  • include 可以将目标文件中除了 <template/> <wxs/> 外的整个代码引入,相当于是拷贝到 include 位置

# WXSS 样式

小程序编译后,rpx会做一次px换算。换算是以375个物理像素为基准,也就是在一个宽度为375物理像素的屏幕下,1rpx = 1px。
举个例子:iPhone6屏幕宽度为375px,共750个物理像素,那么1rpx = 375 / 750 px = 0.5px。

在小程序中,样式引用是这样写:@import './test_0.wxss'
由于WXSS最终会被编译打包到目标文件中,用户只需要下载一次,在使用过程中不会因为样式的引用而产生多余的文件请求。

# JavaScript 脚本

在小程序中, iOS9和iOS10 所使用的运行环境并没有完全的兼容到 ECMAScript 6 标准。 为了帮助开发者解决这类问题,小程序IDE提供语法转码工具帮助开发者,开发者需要在项目设置中,勾选 ES6 转 ES5 开启此功能。

# 模块化

浏览器中,所有 JavaScript 是在运行在同一个作用域下的,定义的参数或者方法可以被后续加载的脚本访问或者改写。 同浏览器不同,小程序中可以将任何一个JavaScript 文件作为一个模块,通过module.exports 或者 exports 对外暴露接口。

# 作用域

在文件中声明的变量和函数只在该文件中有效,不同的文件中可以声明相同名字的变量和函数,不会互相影响。 当需要使用全局变量的时,通过使用全局函数 getApp() 获取全局的实例,并设置相关属性值,来达到设置全局变量的目的。

// 获取全局变量
var global = getApp()
global.globalValue = 'globalValue'
1
2
3

# 请求超时

小程序request默认超时时间是60秒,在小程序项目根目录里边的app.json可以指定request的超时时间。

{
  "networkTimeout": {
    "request": 3000
  }
}
1
2
3
4
5

# 缓存限制和隔离

小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为10MB, 如果当前缓存已经达到10MB,再通过wx.setStorage写入缓存会触发fail回调。

小程序的本地缓存不仅仅通过小程序这个维度来隔离空间,考虑到同一个设备可以登录不同微信用户,宿主环境还对不同用户的缓存进行了隔离,避免用户间的数据隐私泄露。

# 原生组件渲染限制

原生组件脱离在WebView渲染流程外,这带来了一些限制。最主要的限制是一些CSS样式无法应用于原生组件,例如, 不能在父级节点使用overflow:hidden来裁剪原生组件的显示区域;不能使用transformrotate让原生组件产生旋转等。

开发者最为常见的问题是,原生组件会浮于页面其他组件之上(相当于拥有正无穷大的z-index值)使其它组件 不能覆盖在原生组件上展示。想要解决这个问题,可以考虑使用cover-view和cover-image组件。这两个组件也是原生组件, 同样是脱离WebView的渲染流程外,而原生组件之间的层级就可以按照一定的规则控制。

# 数据通信

在数据传输时,逻辑层会执行一次JSON.stringify来去除掉setData数据中不可传输的部分,之后将数据发送给视图层。 同时,逻辑层还会将setData所设置的数据字段与data合并,使开发者可以用this.data读取到变更后的数据。 因此,为了提升数据更新的性能,开发者在执行setData调用时,最好遵循以下原则:

  1. 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用;
  2. 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据;
  3. 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下。

# 视图层渲染

初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将data和setData数据套用在WXML片段上, 得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。 最后,将setData数据合并到data中,并用新节点树替换旧节点树,用于下一次重渲染。

在进行当前节点树与新节点树的比较时,会着重比较setData数据影响到的节点属性。 因而,去掉不必要设置的数据、减少setData的数据量也有助于提升这一个步骤的性能。

# 异常

小程序基础库在WebView侧使用window.onerror方案进行捕捉异常,在逻辑层AppService侧通过把App实例和Page实例 的各个生命周期等方法包裹在try-catch里进行捕捉异常。同时在App构造器里提供了onError的回调,当业务代码运行产生异常时 ,这个回调被触发,同时能够拿到异常的具体信息,开发者自己根据业务情况处理对应的容错逻辑。