Skip to main content

总结

启动优化

骨架屏

是什么

  • 骨架屏是页面的一个空白版本,通常会在页面完全渲染之前,通过一些灰色的区块大致勾勒出轮廓,待数据加载完成后,再替换成真实的内容.

为什么

  • 骨架屏在这个页面白屏的时候,它给了用户及时的反馈,减缓了用户焦急等待的一个情绪,这是它的意义所在。

怎么用

  • 在微信开发者工具,点击右下角,点击生成骨架屏,会生成一个wxml和一个wxss页面。
  • 开发者可以在page.onload拉取数据加载之前,使用骨架屏和Loading提示,在这个数据完成之后将骨架屏和Loading做不渲染的一个处理,再展示真正的一个页面内容。
  • 可在 project.config.json 增加字段 skeletonConfig 进行骨架屏相关配置,页面配置会覆盖掉全局配置。

优缺点

优点
  • 骨架屏在这个页面白屏的时候,它给了用户及时的反馈,减缓了用户焦急等待的一个情绪
缺点
  • 从渲染效率上来讲,骨架屏它并不能使首屏渲染加快。由于骨架屏的一些使用又向用户渲染了额外的一些内容,这些内容是额外添加的、本来是不需要渲染的,它反而从整体上加长了首屏渲染的一个时长。
  • 不要过度去使用骨架屏。

参考文档

按需注入

是什么

  • 程序提供了懒加载机制,允许首屏渲染之前按需注入仅这个首屏运行所需要的一个代码,其他的代码可以在稍后用到的时候再加载和注入

为什么

  • 代码注入在小程序冷启动中是不可或缺的,只有逻辑层和视图层代码全部注入,并且时间点对齐以后才会开始第三阶段首屏渲染的工作。如果这个小程序的这个代码它很大、代码很复杂,又或者用户的设备是低端机,性能不太好,这一阶段便会显著影响启动性能。

怎么用

  • 启动按需注入只需要在app.json配置文件里面添加一个lazyCodeLoading的配置

优缺点

优点
  • 可以优化代码注入环节的耗时和内存占用
缺点
  • 如果这个页面本身它比较复杂,用到了很多自定义的组件,这些自定义组件在开启按需注入这种模式以后仍然是会加载的,如果我们想进一步减少这个首屏需要注入的代码,可以在启用按需注入以后同时启用占位组件

参考文档

初始渲染缓存

是什么

  • 初始渲染缓存,可以使视图层不需要等待逻辑层初始化完毕,而直接提前将页面初始 data 的渲染结果展示给用户。
  • 工作原理
    • 在小程序页面第一次被打开后,将页面初始数据渲染结果记录下来,写入一个持久化的缓存区域(缓存可长时间保留,但可能因为小程序更新、基础库更新、储存空间回收等原因被清除);
    • 在这个页面被第二次打开时,检查缓存中是否还存有这个页面上一次初始数据的渲染结果,如果有,就直接将渲染结果展示出来;
    • 如果展示了缓存中的渲染结果,这个页面暂时还不能响应用户事件,等到逻辑层初始化完毕后才能响应用户事件。

为什么

  • 在启动页面时,尤其是小程序冷启动、进入第一个页面时,逻辑层初始化的时间较长。在页面初始化过程中,用户将看到小程序的标准载入画面或可能看到轻微的白屏现象。

怎么用

  • 静态初始渲染缓存
    • 在页面或app的json文件中添加配置项 "initialRenderingCache": "static"
    • 只包含页面初始data的渲染结果,即页面的纯静态成分
  • 动态初始渲染缓存
    • 在页面或app的json文件中添加配置项 "initialRenderingCache": "dynamic"
    • 在页面中调用 this.setInitialRenderingCache(dynamicData) 才能启用
    • 缓存可以包含setData等动态内容,
    • this.setInitialRenderingCache 调用时机不能早于 Page 的 onReady 或 Component 的 ready 生命周期,否则可能对性能有负面影响

优缺点

优点
  • 快速展示出页面中永远不会变的部分,如导航栏;
  • 预先展示一个骨架页,提升用户体验
缺点
  • 在初始渲染缓存阶段中,复杂组件不能被展示或不能响应交互。
  • 缓存的页面它是无法响应用户的交互事件的
  • 目前支持的内置组件<view /> <text /> <button /> <image /> <scroll-view /> <rich-text />

参考文档

初始渲染缓存与骨架屏区别

  • 初始渲染缓存只适合这个页面节点数量比较少、比较简单、内容不经常变化、用户又经常访问同时wxml节点的结构又非常简单的这样的一些入口页面去使用。
  • 初始渲染缓存与骨架屏是同一种优化策略,本质上它们都是以空间换时间,只不过是两个不同方向的一个优化
  • 一般而言我们用了骨架屏以后就不要再使用初始渲染缓存了
  • 用于提供导航功能的一个首页或者是二级页面,一般适合使用初始渲染缓存
  • 这个页面经常变化的时候,它时效性较高的动态详情页面,这种页面它适合使用骨架屏

分包加载

是什么

  • 分包:将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
  • 独立分包:是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。
  • 分包预下载:在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度
  • 分包异步化:除了非独立分包可以依赖主包外,分包之间不能互相使用自定义组件或进行 require。「分包异步化」特性将允许通过一些配置和新的接口,使部分跨分包的内容可以等待下载后异步使用,从而一定程度上解决这个限制。

为什么

  • 对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
  • 目前小程序分包大小有以下限制:
    • 整个小程序所有分包大小不超过 20M
    • 单个分包/主包大小不能超过 2M

怎么用

  • 主包一般情况下只放一个首页以及基础的不能再基础不能再删减的内容,像其他的这些页面其他的这些自定义组件,在分包实践里面完全可以移到分包里面去。
  • 对于准备在微信中分享的相对独立的页面,准备在分享传播的这些页面适合定义为独立分包,独立分包有返回主页的按钮,一般我们在独立分包里面设置对主包的一个分包预下载
  • 使用分包:
    • app.jsonsubpackages字段声明项目分包结构
  • 独立分包:
    • app.jsonsubpackages字段中对应的分包配置项中定义independent字段声明对应分包为独立分包
  • 分包预下载:
    • 通过在app.json增加preloadRule配置来控制。
  • 分包异步化:
    • 组件
      • 通过为其他分包的自定义组件设置 占位组件,我们可以先渲染占位组件作为替代,在分包下载完成后再进行替换
      • 通过在页面的json文件增加componentPlaceholder配置来实现占位组件。
    • js代码
      • 一个分包中的代码引用其它分包的代码时,为了不让下载阻塞代码运行,我们需要异步获取引用的结果
      • 使用require或才require.async异步调用

优缺点

优点
  • 对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
缺点
  • 使用分包会有各种限制。
  • 使用分包:
    • tabBar 页面必须在主包内
    • subpackage不能嵌套
    • packageA 无法使用 packageB 的资源,但可以使用主包、packageA 内的资源
  • 独立分包:
    • 独立分包中不能依赖主包和其他分包中的内容,包括 js 文件、template、wxss、自定义组件、插件等
    • 主包中的app.wxss对独立分包无效,应避免在独立分包页面中使用app.wxss中的样式;
    • App 只能在主包内定义,独立分包中不能定义 App,会造成无法预期的行为
    • 独立分包中暂时不支持使用插件
    • 与普通分包不同,独立分包运行时,App 并不一定被注册,因此 getApp() 也不一定可以获得 App 对象
    • 当从独立分包启动小程序时,主包中 App 的 onLaunch 和首次 onShow 会在从独立分包页面首次进入主包或其他普通分包页面时调用
    • 由于独立分包中无法定义 App,小程序生命周期的监听可以使用 wx.onAppShow,wx.onAppHide 完成。App 上的其他事件可以使用 wx.onError,wx.onPageNotFound 监听。

参考文档

运行时优化

使用虚拟dom,优化长列表显示

是什么

  • 一个使用虚拟滚动的组件,用来优化长列表内容的渲染.在渲染的时候仅会渲染用户当前在这个视图里面应该看到的内容,对于看不到的内容则不会渲染,使用这个组件不仅可以加速首屏渲染的一个速度,还可以让运行时的列表滑动浏览更加的流畅。
  • 文档链接

为什么

  • 电商小程序往往需要展示很多商品,当一个页面展示很多的商品信息的时候,会造成小程序页面的卡顿以及白屏。原因有如下几点:
    • 商品列表数据很大,首次 setData 的时候耗时高
    • 渲染出来的商品列表 DOM 结构多,每次 setData 都需要创建新的虚拟树、和旧树 diff 操作耗时都比较高
    • 渲染出来的商品列表 DOM 结构多,占用的内存高,造成页面被系统回收的概率变大。
  • 因此实现长列表组件来解决这些问题。

怎么用

  • 安装组件
  • 在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置
  • WXML 文件中引用 recycle-view
  • 页面 JS 管理 recycle-view 的数据

优缺点

优点
  • 明显地减少小程序页面的卡顿以及白屏
  • 很好地实现虚拟DOM这样的一个优化思想
  • 本身已经开启了throttle函数节流限制,减少不必要的 多余的CPU浪费 资源消耗
  • 预留了插槽。我们可以看到它有before以及after slot方便开发者添加个性的业务逻辑
缺点
  • 首先对于 view 和 item 的结构是清晰的,但是对于数据需要手动通过 ctx.append 进行追加,而且对于整个 recycle-view 和 recycle-item 的处理逻辑是要和业务层耦合在一起的,这种方式对于小程序的开发者有一定技术熟练度的要求。
  • 必须设置每个item的宽度和高度

其他文档

小程序长列表优化实践

优化重渲染

是什么

  • 视图如果需要改变,需要逻辑层通过setData方法、改变视图上面绑定的数据。当数据发生变化的时候,小程序会将原数据与新数据进行结合,拿到一个新的节点树并拿新的节点树与的原节点树进行一个差异比较,以此来获得哪些节点的属性需要更新、哪些节点需要添加或者是移除等等这些信息

为什么

  • 从渲染机制我们可以看出来每次通过setData传递数据,它不仅受到底层传输通道窄小的限制,它还受到设备渲染能力的一个限制。这个数据越大节点越多越复杂。它渲染也就越慢,而渲染一慢在用户侧看来它表现起来就是卡顿 操作反馈不及时。
  • 我们可以将这个界面功能进行组件化处理,将频繁变化的数据封装在一个个的单独的组件里边,同时去掉一些不必要的数据设置,减少每次setData传递的一个数据量,也可以提升这个视图的渲染效率
  • 还有通过wxs脚本改写组件,让可以在这个视图层里面完成的代码逻辑,就在这个视图层里面进行完成。

怎么用

  • 组件化
  • 在wxs脚本与逻辑层需要相互调用
    • 在wxs脚本里面可以通过ownerInstance的callMethod方法去调用这个页面上的方法
    • 逻辑层怎么样去调用wxs脚本里面的方法,我们可以通过视图层上绑定一个名称,为change:xxx这样的一个特别属性,触发对wxs脚本的里面的方法的一个调用

优缺点

优点
  • 组件化:减少重绘制的范围,优化setData数据量大小。
  • wxs优化:减少通信的次数,让事件在视图层(Webview)响应
缺点
  • 将一些业务逻辑放在wxs脚本执行,能加快一点执行时间,但是不多。在wxs中还是要通过setData去更新视图,主要时间还是setData时间上。

其他文档

WXS响应事件 [重渲染与自定义组件优化]https://developers.weixin.qq.com/community/business/doc/0000cecf3b87d0edc0dd311025680d

wxml代码优化

以下是优化小程序视图代码的几个小技巧:

  1. 使用 wxml 标签要克制,能不用容器标签就不用,能少用标签就少用标签。
  2. 默认使用 catch 代替 bind 绑定事件,避免没必要的冒泡,自定义的 data 数据属性里边永远只存储基本的数据类型,并且只存储小数据。
  3. 在动态渲染的列表里,一定要绑定一个唯一的 wx:key,静态渲染可以使用 index。
  4. 对于 scroll 高频事件,要节流使用节流函数 throttle。
  5. 对于用户的单击事件,尤其是高频触发的单击事件,可以适当使用防抖函数 debounce。

优化视图代码,就是优化对 wxml 标签及其属性的一个使用。如果页面足够复杂,标签嵌套足够深,Parse WXML 的过程耗时会很长。wxml 和 wxss 经过解析后才能完成视图层代码注入,如果这个过程很长,会影响冷启动的总时间。

wxss代码优化

  • 删除无用的样式代码是为了提高wxss样式的命中率,可以使用gulp-cleanwxss插件清除无用的wxss代码

  • 某些样式只在分包里使用,不要在主包里引入

  • 某个子页面只使用在该页面内定义样式,不要在全局的app.wxss文件中引用

  • 某个组件只在组件内定义样式

  • 减少公共样式的引入可以提高wxss样式的命中率和小程序启动速度

  • 在安卓下默认有惯性滚动,而在iOS下面需要额外设置,添加样式:-webkit-overflow-scrolling:touch,便可以开启惯性滚动了

  • 在小程序开发里面使用:active伪类可以实现按钮的单击态效果,但是这种实现存在一个缺点,事件太容易触发,并且滚动态滑动的时候,单击态不容易消失,在一些旧的iOS设备上又容易失效。使用内置的hover-class属性实现单击态

JS代码优化

  • 定时器在离开页面时一定要及时销毁,否则可能导致内存泄漏。
  • 全局监听事件使用时要注意及时添加和移除监听,以避免无效页面被异步线程引用。比如使用wx.onXxx等事件
  • 使用全局对象时要注意及时清理,如自定义的类对象需要实现dispose方法,在释放对象时先调用dispose方法,再从全局对象上删除。
  • 使用this对象时需要特别谨慎,尤其是在周期性发生的异步回调函数中,绑定的this对象不是一个简单的对象,需要进行优化。
  • 引用类型的变量尤其是不断增长的临时变量,不要被全局对象或异步线程对象引用或间接引用,在使用完成后要及时进行清理。

setData优化技巧

  • 不要多次分开调用setData,尽量要合并调用
  • 不准备渲染的数据不要放在data数据对象里边
  • 使用索引法进行局部更新长列表数据,例如将this.setData({list:list}),替换为this.setData({'list[i].open':true})
  • 当小程序进入后台状态的时候,会有5秒的一个暂停状态,这时小程序的代码仍然是可以运行的。这里应该禁止用setData方法,可以通过对象劫持的方法,去禁用setData方法。参考文档

网络请求优化

  • 减少不必要的网络请求,使用本地缓存的数据代替从后端接口拉取的数据。先使用本地缓存加载数据,接着再去拉取接口数据,如果接口数据有更新再更新缓存
  • 优化网络请求的并发数,让优先级高的请求先执行。小程序默认的最大请求数是10个,可以通过使用priority-async-queue的npm包来设置请求的优先级。
  • 优化网络请求参数,提高网络请求的通讯效率。wx.request是微信小程序,向后端发起网络请求的接口已经提供了一些优化参数
    • enableCache是缓存内容,相同请求它优先读取本地的内容 这个参数是可以开启的
    • enableHttp2 这个参数是尝试使用HTTP2协议,如果这个后端服务器支持的话,这个参数也可以开启
    • enableQuic,这是尝试使用Quic协议,Quic是被称为第三代网络协议,它拥有更好的网络传输性能,这个参数也可以开启

图片优化

  • 尽量减少图片的请求次数,小图片可以使用雪碧图。
  • 尽量压缩图片的大小
  • 尽量使用带有CDN加速的网络图片链接
  • 尽可能使用高压缩比的图片,例如webp格式的图片

其他代码优化技巧

  • 将拉取接口数据的时机,尽量提前,可以放在app.onLoad中去,然后通过异步数据订阅的机制,在page.onload中去执行异步结果。参考文档

总结

以上优化富有一些创造性

  • 第一点,使用异步转同步编程范式,结合立即执行函数,在保证代码清晰性,可阅读性的前提之下提高主线程的执行效率。
  • 第二点,将优先级自动排序的列队应用于网络请求当中提高网络操作的执行效率。
  • 第三点,使用串发复合指令,在多个文件里面延迟非重要同步代码的执行。
  • 第四点,使用并发复合指令,在多个文件中对齐代码执行点,使用竞赛模式基于多种渠道竞相拉取主页数据。

在优化策略上,整体来讲大体可以分为两类。

  • 第一类以空间换时间,例如数据预拉取,周期性更新,初始渲染缓存,使用LocalStorage缓存接口等等这些技巧,都属于以空间换时间的技巧。
  • 第二类以时间换空间,例如使用虚拟DOM,使用长列表组件,这一类都是属于以时间换空间的优化技巧
  • 本质上精神都是一致的。精神就是现人现地,立足小程序的启动流程,双线程运行机制和重渲染机制,具体项目具体分析,以一个字节,一个字节去抠,一个毫秒,一个毫秒去节省的细致精神,一点点进行优化。在这个地方抠下了一点,在那个地方省下了一点,整体上性能就提升了。