Skip to content

运行原理 ​

逻辑层和视图层分离,且非 H5 端通信有折损 ​

qinjs 在非H5端运行时,从架构上分为逻辑层和视图层两个部分。逻辑层负责执行业务逻辑,也就是运行js代码,视图层负责页面渲染。

虽然开发者在一个vue页面里写js和css,但其实,编译时就已经将它们拆分了。

逻辑层详解 ​

逻辑层是运行在一个独立的 jscore 里的,它不依赖于本机的 webview ,所以一方面它没有浏览器兼容问题,可以在Android4.4上跑es6代码,另一方面,它无法运行 window 、 document 、 navigator 、 localstorage 等浏览器专用的js API。

jscore 就是一个标准 js 引擎,标准 js 是可以正常运行的,比如 if 、 for 、各种字符串、日期处理等。 js 和浏览器的区别要注意区分开来。

  • 所谓浏览器的 js 引擎,就是jscore或v8的基础上新增了一批浏览器专用 API ,比如 dom ;
  • node.js 引擎,则是v8基础上补充一些电脑专用API,比如本地io;
视图层详解 ​

h5 和小程序平台,视图层是 webview 。

关于 webview ,在 iOS 上,只能使用 iOS 提供的 Webview(默认是WKWebview)。它有一定的浏览器兼容问题,iOS版本不同,它的表现有细微差异(一般可忽略)。

Android 上小程序大多自带了一个几十M的 chromium webview 。

所以 qinjs 的 js 基本没有不同手机的兼容问题(因为 js 引擎自带了),而视图层的css。此时或者不要用太新的 css 语法,或者集成腾讯 x5 引擎。

逻辑层和视图层分离的利与弊 ​

逻辑层和视图层分离,好处是 js 运算不卡渲染,最简单直接的感受就是:窗体动画稳。

如果开发者使用过 App ,应该有概念,webview新窗体一边做进入动画,一边自身渲染,很容易卡动画。而 qinjs 则无需写预载代码,新窗体渲染快且动画稳定。

但是两层分离也带来一个坏处,这两层互相通信,其实是有损耗的。

iOS 还好,但 Android 低端机上,每次通信都要耗时几十毫秒。平时看不出来影响,但有几个场景表现明显。

  1. 连续高帧率绘制canvas动画,会发现还不如 webview 内部绘制流畅
  2. 视图层滚动、跟手操作,不停反馈给逻辑层, js 再处理逻辑并通知视图层做对应更新。此时会发现交互不跟手或卡

小程序有这个两层通信损耗的问题。

解决这类问题,在 webview 渲染和原生渲染引用了不同的做法:

  • webview 渲染的视图层

在微信小程序上,提供了一种运行于视图层的专属js,微信叫做 wxs。

wxs 中可以监听手势,以 uni ui的 swiperAction 组件为例,手指拖动,侧边的列表菜单项要跟手滑出,此时就需要使用 wxs 才能实现流畅效果。还有插件市场里一些自定义下拉刷新的插件,通过 wxs 实现了更高的性能体验。

qinjs 支持把 wxs编译到微信小程序和H5中。

微信里对 wxs 限制较多,只能实现有限的功能。

比如canvas动画,微信的canvas无法通过wxs操作,js不停绘制canvas动画因通信折损而无法流畅。uni-app的app-vue里的canvas对象设计在webview视图层的,通过renderjs可以在视图层直接操作canvas动画,将不再有通信折损,实现更流畅的效果,详见:renderjs

小程序的数据更新,分页面级和组件级 ​

对于复杂页面,更新某个区域的数据时,需要把这个区域做成组件,这样更新数据时就只更新这个组件,否则会整个页面的数据更新,造成点击延迟卡顿。

比如微博长列表页面,点击一个点赞图标,赞数要立即+1,此时这个点赞按钮一定要做成组件。否则这个+1会引发页面级所有数据的从js层向视图层的同步。

h5 不存在此问题。造成差异的原因是小程序目前只提供了组件差量更新的机制,不能自动计算所有页面差量。

优化建议 ​

避免使用大图 ​

页面中若大量使用大图资源,会造成页面切换的卡顿,导致系统内存升高,甚至白屏崩溃。

尤其是不要把多张大图缩小后显示在一个屏幕内,比如上传图片前选了数张几 M 体积的照片,然后缩小在一个屏幕中展示多张几 M 的大图,非常容易白屏崩溃。

对大体积的二进制文件进行 base64 ,也非常耗费资源。

优化数据更新 ​

在 qinjs 中,定义在 data 里面的数据每次变化时都会通知视图层重新渲染页面。所以如果不是视图所需要的变量,可以不定义在 data 中,可在外部定义变量或直接挂载在 vue 实例上,以避免造成资源浪费。

长列表 ​
  • 长列表中如果每个 item 有一个点赞按钮,点击后点赞数字+1,此时点赞组件必须是一个单独引用的组件,才能做到差量数据更新。否则会造成整个列表数据重载。
  • 长列表中每个 item 并不一定需要做成组件,取决于你的业务中是否需要差量更新某一行 item 的数据,如没有此类需求则不应该引入大量组件。(点击 item 后背景变色,属于 css 调整,没有更新 data 数据和渲染,不涉及这个问题)
  • 单个组件中存在大量数据时(比如长列表),在App和小程序端数据更新时会消耗较多时间,建议使用组件对数据进行分页,将变更限制更小范围。可以参考:长列表优化示例
  • 如需要左右滑动的长列表,请在HBuilderX新建uni-app项目选新闻模板,那是一个标杆实现。自己用swiper和scroll-view做很容易引发性能问题。
减少一次性渲染的节点数量 ​

页面初始化时,逻辑层如果一次性向视图层传递很大的数据,使视图层一次性渲染大量节点,可能造成通讯变慢、页面切换卡顿,所以建议以局部更新页面的方式渲染页面。如:服务端返回100条数据,可进行分批加载,一次加载50条,500ms 后进行下一次加载。

减少组件数量、减少节点嵌套层级 ​

深层嵌套的节点在页面初始化构建时往往需要更多的内存占用,并且在遍历节点时也会更慢些,所以建议减少深层的节点嵌套。

避免视图层和逻辑层频繁进行通讯 ​
  • 减少 scroll-view 组件的 scroll 事件监听,当监听 scroll-view 的滚动事件时,视图层会频繁的向逻辑层发送数据;
  • 监听 scroll-view 组件的滚动事件时,不要实时的改变 scroll-top/scroll-left 属性,因为监听滚动时,视图层向逻辑层通讯,改变 scroll-top/scroll-left 时,逻辑层又向视图层通讯,这样就可能造成通讯卡顿。
  • 注意 onPageScroll 的使用,onPageScroll 进行监听时,视图层会频繁的向逻辑层发送数据;
  • 多使用css动画,而不是通过js的定时器操作界面做动画
  • 如需在canvas里做跟手操作,app端建议使用renderjs,小程序端建议使用web-view组件。web-view里的页面没有逻辑层和视图层分离的概念,自然也不会有通信折损。
优化页面切换动画 ​
  • 页面初始化时若存在大量图片或原生组件渲染和大量数据通讯,会发生新页面渲染和窗体进入动画抢资源,造成页面切换卡顿、掉帧。建议延时100ms~300ms渲染图片或复杂原生组件,分批进行数据通讯,以减少一次性渲染的节点数量。
  • H5,还支持页面预载,qin.preloadPage,可以提供更好的使用体验
优化启动速度 ​
  • 工程代码越多,包括背景图和本地字体文件越大,对小程序启动速度有影响,应注意控制体积。组件引用的前景图不影响性能。
优化包体积 ​
  • qinjs 发行到小程序时,自带引擎只有几十K,主要是一个定制过的 vue.js 核心库。如果使用了 es6 转 es5 、 css 对齐的功能,可能会增大代码体积,可以配置这些编译功能是否开启。
  • qinjs 的 H5 端,自带了 vue.js 、 vue-router 及部分 es6 polyfill 库,这部分的体积 gzip 后只有 92k ,和 web 开发使用 vue 基本一致。而内置组件 ui 库(如 picker 、 switch 等)、小程序的对齐 js api 等,相当于一个完善的大型 ui 库 。但大多数应用不会用到所有内置组件和 API 。由此 qinjs 提供了摇树优化机制,未摇树优化前的 qinjs 整体包体积约500k,服务器部署 gzip 后 162k 。开启摇树优化需在 manifest 配置,详情。