小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
缺点:无法动态打包,动态下发。
缺点:如果我们用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。
从渲染底层来看,PhoneGap 与微信 JS-SDK 是类似的,它们最终都还是使用浏览器内核来渲染界面。而 RN 则不同,虽然是用 Web 相关技术来编写,同样是利用了 JavaScript 解释执行的特性,但 RN 在渲染底层是用客户端原生渲染的。我们选择类似于微信 JSSDK 这样的 Hybrid 技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的 WebView 去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个 WebView 的任务过于繁重。
不选择 RN 的原因?
在安卓则是往 WebView 的 window
对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了 调用(invoke)和 监听(on)这两种方法。开发者插入一个原生组件,一般而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。
由于 JavaScript 的灵活性和浏览器的功能丰富,会导致很多不可控的隐私,因此,微信提供了一个单纯的JS执行环境,通过对于其中的控件也进行了自定义。因此完全采用这个沙箱环境不能有任何浏览器相关接口,只提供纯 JavaScript 的解释执行环境,那么像 HTML5 中的 ServiceWorker、WebWorker 特性就符合这样的条件,这两者都是启用另一线程来执行 JavaScript。但是考虑到小程序是一个多 WebView 的架构,每一个小程序页面都是不同的 WebView 渲染后显示的,在这个架构下我们不好去用某个 WebView 中的 ServiceWorker 去管理所有的小程序页面。得益于客户端系统有 JavaScript 的解释引擎(在 iOS 下是用内置的 JavaScriptCore 框架,在安卓则是用腾讯 x5 内核提供的 JsCore 环境),我们可以创建一个单独的线程去执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是我们前面一直提到的逻辑层。而界面渲染相关的任务全都在 WebView 线程里执行,通过逻辑层代码去控制渲染哪些界面,那么这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。
为了防止标签定义带来的一些问题,微信自定义了一套标签语言 WXML,这套标签语言经过编译之后,最终会生成 HTML。
小程序的渲染层和逻辑层分别由 2 个线程管理:
一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由微信客户端(下文中也会采用 Native 来代指微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型下图所示。
在开发 UI 界面过程中,程序需要维护很多变量状态,同时要操作对应的 UI 元素。随着界面越来越复杂,我们需要维护很多变量状态,同时要处理很多界面上的交互事件,整个程序变得越来越复杂。通常界面视图和变量状态是相关联的,如果有某种 方法 可以让状态和视图绑定在一起(状态变更时,视图也能自动变更),那我们就可以省去手动修改视图的工作。
小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把 WXML 转化成对应的 JS 对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的 setData
方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的 DOM 树上,渲染出正确的 UI 界面。
通过 setData
把 msg
数据从 “Hello World”
变成 “Goodbye”
,产生的 JS 对象对应的节点就会发生变化,此时可以对比前后两个 JS 对象得到变化的部分,然后把这个差异应用到原来的 DOM 树上,从而达到更新 UI 的目的,这就是 数据驱动 的原理。
UI 界面的程序需要和用户互动,例如用户可能会点击你界面上某个按钮,又或者长按某个区域,这类反馈应该通知给开发者的逻辑层,需要将对应的处理状态呈现给用户。由于 WebView 现在具备的功能只是进行渲染,因此对于事件的分发处理,微信进行了特殊的处理,将所有的事件拦截后,丢到逻辑层交给 JavaScript 进行处理。
事件的派发处理,具备事件捕获和冒泡两种机制。通过 Native 传递给 JSCore,通过 JS 来响应响应的事件之后,对 DOM 进行修改,改动会体现在虚拟 DOM 上,然后再进行真实的渲染。
小程序是基于 双线程模型,那就意味着任何数据传递都是线程间的通信,也就是都会有一定的延时。这不像传统 Web 那样,当界面需要更新时,通过调用更新接口 UI 就会同步地渲染出来。在小程序架构里,这一切都会变成异步。
异步会使得各部分的运行时序变得复杂一些。比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来,如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作。因此逻辑层与渲染层需要有一定的机制保证时序正确,
在每个小程序页面的生命周期中,存在着若干次页面数据通信。逻辑层向视图层发送页面数据(data
和 setData
的内容),视图层向逻辑层反馈用户事件。
通过 JSON 的方式进行数据的传递,提高性能的方式就是减少交互的数据量。
小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为 10MB,如果当前缓存已经达到 10MB,再通过 wx.setStorage
写入缓存会触发 fail
回调。
小程序的本地缓存不仅仅通过小程序这个维度来隔离空间,考虑到同一个设备可以登录不同微信用户,宿主环境还对不同用户的缓存进行了隔离,避免用户间的数据隐私泄露。
由于本地缓存是存放在当前设备,用户换设备之后无法从另一个设备读取到当前设备数据,因此用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。
微信小程序运行在多种平台上:iOS(iPhone/iPad)微信客户端、Android 微信客户端、PC 微信客户端、Mac 微信客户端和用于调试的微信开发者工具。
各平台脚本执行环境以及用于渲染非原生组件的环境是各不相同的:
小程序启动后,界面被展示给用户,此时小程序处于 前台 状态。
当用户点击右上角胶囊按钮关闭小程序,或者按了设备 Home 键离开微信时,小程序并没有完全终止运行,而是进入了后台状态,小程序还可以运行一小段时间。
当用户再次进入微信或再次打开小程序,小程序又会从后台进入 前台。但如果用户很久没有再进入小程序,或者系统资源紧张,小程序可能被 销毁,即完全终止运行。
这样,小程序启动可以分为两种情况,一种是 冷启动,一种是 热启动。
通常,只有当小程序进入后台一定时间,或者系统资源占用过高,才会被销毁。具体而言包括以下几种情形:
运行内存不足,请重新打开该小程序
。具体策略会持续进行调整优化。小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的。
网页 开发渲染线程 和 脚本线程 是 互斥 的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在 不同的线程 中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。
网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ 浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具,小程序中三大运行环境也是有所区别的,
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS | JavaScriptCore | WKWebView |
安卓 | V8 | Chromium 定制内核 |
小程序开发者工具 | NWJS | Chrome WebView |
网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程方可完成。
参考资料: