双线程模型
- 微信小程序的框架包含两部分 View 视图层、App Service逻辑层。View层用来渲染页面结构,App Service层用来逻辑处理、数据请求、接口调用,它们在两个线程(Webview)里运行。
- 视图层和逻辑层通过系统层的JSBridage进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
小程序的渲染层和逻辑层分别由2个线程管理:
(1)视图层:界面渲染相关的任务全都在 WebView 线程里执行。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程。
(2)逻辑层:采用 JsCore 线程运行JS脚本。
视图层和逻辑层通过系统层的 WeixinJsBridage 进行通信:逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。
渲染流程
把开发者的 JS 逻辑代码放到单独的线程去运行,但在 Webview 线程里,开发者就没法直接操作 DOM。
那要怎么去实现动态更改界面呢?
逻辑层和试图层的通信会由 Native (微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。
这也就是说,我们可以把 DOM 的更新通过简单的数据通信来实现。
Virtual DOM 大概是这么个过程:用 JS 对象模拟 DOM 树 -> 比较两棵虚拟 DOM 树的差异 -> 把差异应用到真正的 DOM 树上。
页面渲染的具体流程是:在渲染层,宿主环境会把 WXML 转化成对应的 JS 对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的 setData 方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。
(1)在渲染层把 WXML 转化成对应的 JS 对象。
(2)在逻辑层发生数据变更的时候,通过宿主环境提供的 setData 方法把数据从逻辑层传递到 Native,再转发到渲染层。
(3)经过对比前后差异,把差异应用在原来的 DOM 树上,更新界面。
我们通过把 WXML 转化为数据,通过 Native 进行转发,来实现逻辑层和渲染层的交互和通信。
双线程模型设计的好处
双线程模型是小程序框架与业界大多数前端 Web 框架不同之处。基于这个模型,可以更好地管控以及提供更安全的环境。缺点是带来了无处不在的异步问题(任何数据传递都是线程间的通信,也就是都会有一定的延时),不过小程序在框架层面已经封装好了异步带来的时序问题。
为什么要这样设计呢,前面也提到了管控和安全,为了解决这些问题,我们需要阻止开发者使用一些,例如浏览器的window对象,跳转页面、操作DOM、动态执行脚本的开放性接口。
我们可以使用客户端系统的 JavaScript 引擎(iOS 下的 JavaScriptCore 框架,安卓下腾讯 x5 内核提供的 JsCore 环境),这个沙箱环境只提供纯 JavaScript 的解释执行环境,没有任何浏览器相关接口,这就是小程序双线程模型的由来。
性能优化
主要的优化策略可以归纳为三方面:
- 启动小程序方面: - 分包加载- 控制代码包大小
- 页面及页面层级方面 - 合理使用setData调用,减少setData次数和数据量
- 数据通信方面:精简代码,降低WXML结构和JS代码的复杂性。 - js中提高数据更新速度- wxml 中提高数据更新速度
小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。
性能优化方式一:分包加载
采用分包加载时,小程序的代码包有两种:
一个“主包”,包含小程序启动时会马上打开的页面代码和相关资源。
多个“分包”,包含其余的代码和资源。
这样,小程序启动时,只需要先将主包下载完成,就可以立刻启动小程序,从而降低小程序代码包的下载时间。
分包app.json 中的配置如下
{//主包"pages":["pages/index/index","pages/logs/logs"],//分包"subPackages":[{"root":"packageA","pages":[“pages/apple/apple"]},{"root":"packageB","pages":["pages/banana/banana"]}]}
- 单个分包/主包大小不能超过 2M
- 整个小程序所有分包大小不超过 16M
性能优化方式二:控制代码包大小
精简代码,去掉不必要的WXML结构和未使用的WXSS。
减少在代码包中直接嵌入的资源文件。不是必须的可以放在服务器上。
压缩图片,使用适当的图片格式。
性能优化方式三:合理使用setData调用,减少setData次数和数据量;
1、setData 工作原理
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。
在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。
当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。
2、常见的 setData 操作错误
(1)频繁的去 setData
在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:Android下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;
(2)每次 setData 都传递大量新数据
由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程
(3)后台态页面进行setData
当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。
性能优化方式四:js中提高数据更新速度
多次setData合并成一次setData调用,不要过于频繁调用setData。
数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示,且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据。
与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其它字段下。
例如
onShow:function(){// 不要频繁调用setDatathis.setData({a:1})this.setData({b:2})// 绝大多数时候可优化为this.setData({a:1,b:2})// 将与界面无关的数据放在data外this.setData({myData:{a:'这个字符串在WXML中用到了',b: '这个字符串未在WXML中用到,且很长…’ }})// 可以优化为this.setData({'myData:{a':'这个字符串在WXML中用到了'})this._myData ={b:'这个字符串未在WXML中用到,且很长…'}}
性能优化方式五:wxml 中提高数据更新速度
去掉不必要的事件绑定(WXML中的bind和catch),从而减少通信的数据量和次数。
不要在节点的data前缀属性中放置过大的数据,因为事件绑定时需要传输target和currentTarget的dataset
版权归原作者 浮游本尊 所有, 如有侵权,请联系我们删除。