背景:首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们可以从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。
小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 渲染表现、setData
数据量、元素节点数 和 网络请求延时 这几个维度来给予定义(下面只列出部分关键指标):
setData
的次数不超过 20 次;setData
的数据在 JSON.stringify
后不超过 256kb;详见 小程序性能评分规则
1、我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。
2、这些指标会直接作为小程序体验评分工具的性能评分规则,其集成在开发者工具中,是目前检测小程序性能问题最直接有效的途径。在小程序运行时实时检查相关问题点,并为开发者给出优化建议。
小程序管理平台 和 小程序助手 为开发者提供了大量的真实数据统计。性能分析面板从 启动性能、运行性能 和 网络性能 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。
启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时
为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。
微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。
而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了双线程模型:
任何线程间的数据传输都是有延时的,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线程之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。
作为小程序开发者,我们常常会被下面几个问题所困扰:
所以,我们需要结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。
小程序启动阶段,也就是如下图所示的展示加载界面的阶段。
在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:
1. 准备运行环境:
在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。
2. 下载小程序代码包:
在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。
3. 加载小程序代码包:
小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。
4. 初始化小程序首页:
在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。
综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 控制代码包大小,缩小代码包的下载时间。
①无用文件、函数、样式剔除
经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。
因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。
在小程序中,所有页面的路径都需要在小程序代码根目录 app.json
中被声明,类似地,自定义组件也需要在页面配置文件 page.json
中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。
import api from '../../api/index'; // 如果没有被使用,应当删除
import rule from './config';
JS Tree-Shaking 的原理就是借助 Babel
把代码编译成抽象语法树(AST),通过 AST 获取到函数的调用关系,从而把未被调用的函数方法剔除掉。不过这需要依赖 ES module,而小程序最开始是遵循 CommonJS 规范的,这意味着是时候来一波“痛并快乐着”的改造了。CSS 的 Tree-Shaking 可以利用 PurifyCSS 插件来完成。关于这两项技术,有兴趣的可以“谷歌一下”。
②减少代码包中的静态资源文件
小程序代码包最终会经过 GZIP 压缩放在 CDN 上,但 GZIP 压缩对于图片资源来说效果非常低。如 JPG
、PNG
等格式文件,本身已经被压缩过了,再使用 GZIP 压缩有可能体积更大,得不偿失。所以,除了部分用于容错的图片必须放在代码包(譬如网络异常提示)之外,建议开发者把图片、视频等静态资源都放在 CDN 上。需要注意,Base64
格式本质上是长字符串,和 CDN 地址比起来也会更占空间。
// 应该尽量避免css文件中这种写法,打包后转成base64更占空间
.icon-cart {
background-image: url('../../assets/images/icon/icon-cart.png');
}
.icon-close {
background-image: url('../../assets/images/icon/icon-close.png');
}
③逻辑后移,精简业务逻辑
通过让后台承担更多的业务逻辑,可以节省小程序前端代码量,同时线上问题还支持紧急修复,不需要经历小程序的提审、发布上线等繁琐过程。一般不涉及前端计算的展示类逻辑,都可以适当做后移。
④复用模板插件
如果需要应对各类频繁的营销活动、升级改版等,同时也要满足不同用户属性的界面个性化需求。如何既能减少为应对多样化场景而产生的代码量,又可以提升研发效率,成为燃眉之急。我们需要提供更丰富的可配置能力,实现更高的代码复用度。
当然,要完成这样的插件化改造免不了几个先决条件:
⑤分包加载
小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:
⑥部分页面 h5 化
小程序提供了 web-view 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。小程序和 h5 的通信可以通过 JSSDK 或 postMessage 通道来实现,详见 小程序开发文档。
白屏阶段,是指小程序代码包下载完(也就是启动界面结束)之后,页面完成首屏渲染的这一阶段,也就是 FMP (首次有效绘制)。
对于大部分小程序来说,页面首屏展示的内容都需要依赖服务端的接口数据,那么影响白屏加载时间的主要由这两个元素构成:
①启用本地缓存
小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。
但并非所有场景都适合缓存策略,譬如对数据即时性要求非常高的场景(如抢购入口)来说,展示老数据可能会引发一些问题。
小程序默认会按照 不同小程序、不同微信用户 这两个维度对缓存空间进行隔离。
②数据预拉取
小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力:数据预拉取。关于冷启动和热启动的定义可以看 这里
数据预拉取的原理:在小程序启动时,微信服务器代理小程序客户端发起一个 HTTP 请求到第三方服务器来获取数据,并且把响应数据存储在本地客户端供小程序前端调取。当小程序加载完成后,只需调用微信提供的 API wx.getBackgroundFetchData
从本地缓存获取数据即可。这种做法可以充分利用小程序启动和初始化阶段的等待时间,使更快地完成页面渲染。
但这个能力存在一些不成熟的地方:
由于预拉取的请求最终是由微信的服务器发起的,也许是出于服务器资源限制的考虑,预拉取的数据会缓存在微信本地一段时间,缓存失效后才会重新发起请求。经过真机实测,在微信购物入口冷启动的场景下,预拉取缓存存活了 30 分钟以上,这对于数据实时性要求比较高的系统来说是非常致命的。
由于请求第三方服务器是从微信的服务器发起的,而不是从小程序客户端发起的,所以本地代理无法拦截到这一次真实请求,这会导致开发者无法通过拦截请求的方式来区分获取线上环境和开发环境的数据,给开发调试带来麻烦。
小程序内部接口的响应体类型都是 application/octet-stream
,即数据格式未知,使本地代理无法正确解析。
如果这几个问题点都不会影响到你的场景,那么可以尝试开启预拉取能力,这对于小程序首屏渲染速度是质的提升。
③跳转时预拉取
为了尽快获取到服务端数据,比较常见的做法是在页面 onLoad
钩子被触发时发起网络请求,但其实这并不是最快的方式。从发起页面跳转,到下一个页面 onLoad
的过程中,小程序需要完成一些环境初始化及页面实例化的工作,耗时大概为 300 ~ 400 毫秒。
实际上,我们可以在发起跳转前(如 wx.navigateTo
调用前),提前请求下一个页面的主接口并存储在全局 Promise
对象中,待下个页面加载完成后从 Promise
对象中读取数据即可。
这也是双线程模型所带来的优势之一,不同于多页面 web 应用在页面跳转/刷新时就销毁掉 window 对象。
④分包预下载
如果开启了分包加载能力,在用户访问到分包内某个页面时,小程序才会开始下载对应的分包。当处于分包下载阶段时,页面会维持在 “白屏” 的启动态,这用户体验是比较糟糕的。
幸好,小程序提供了 分包预下载 能力,开发者可以配置进入某个页面时预下载可能会用到的分包,避免在页面切换时僵持在 “白屏” 态。
⑤非关键渲染数据延迟请求
这是关键渲染路径优化的其中一个思路,从缩短网络请求时延的角度加快首屏渲染完成时间。关键渲染路径(Critical Rendering Path) 是指在完成首屏渲染的过程中必须发生的事件。
我们根据小程序首页的页面结构,可以把所有模块划分成两类:主体模块(导航、商品轮播、商品豆腐块等)和 非主体模块(幕帘弹窗、右侧挂件等)。
在初始化首页时,小程序会发起一个聚合接口请求来获取主体模块的数据,而非主体模块的数据则从另一个接口获取,通过拆分的手段来降低主接口的调用时延,同时减少响应体的数据量,缩减网络传输时间。
⑥分屏渲染
这也是关键渲染路径优化思路之一,通过延迟非关键元素的渲染时机,为关键渲染路径腾出资源。我们在 主体模块 的基础上再度划分出 首屏模块(商品豆腐块以上部分) 和 非首屏模块(商品豆腐块及以下部分)。当小程序获取到主体模块的数据后,会优先渲染首屏模块,在所有首屏模块都渲染完成后才会渲染非首屏模块和非主体模块,以此确保首屏内容以最快速度呈现。
⑦接口聚合,请求合并
在小程序中,发起网络请求是通过 wx.request 这个 API。我们知道,在 web 浏览器中,针对同一域名的 HTTP 并发请求数是有限制的;在小程序中也有类似的限制,但区别在于不是针对域名限制,而是针对 API 调用:
wx.request
(HTTP 连接)的最大并发限制是 10 个;wx.connectSocket
(WebSocket 连接)的最大并发限制是 5 个;超出并发限制数目的 HTTP 请求将会被阻塞,需要在队列中等待前面的请求完成,从而一定程度上增加了请求时延。因此,对于职责类似的网络请求,最好采用节流的方式,先在一定时间间隔内收集数据,再合并到一个请求体中发送给服务端。
⑧图片资源优化
图片资源一直是移动端系统中抢占大流量的部分,尤其是对于电商系统。优化图片资源的加载可以有效地加快页面响应时间,提升首屏渲染速度。
WebP 是 Google 推出的一种支持有损/无损压缩的图片文件格式,得益于更优的图像数据压缩算法,其与 JPG、PNG 等格式相比,在肉眼无差别的图片质量前提下具有更小的图片体积(据官方说明,WebP 无损压缩体积比 PNG 小 26%,有损压缩体积比 JPEG 小 25-34%)。小程序的 image 组件 支持 JPG、PNG、SVG、WEBP、GIF 等格式。
鉴于移动端设备的分辨率是有上限的,很多图片的尺寸常常远大于页面元素尺寸,这非常浪费网络资源(一般图片尺寸 2 倍于页面元素真实尺寸比较合适)。我们可以通过资源的命名规则和请求参数来获取服务端优化后的图片:
裁剪成 100x100 的图片:https://{host}/s100x100_jfs/{file_path}
;
降质 70%:https://{href}!q70
;
这两者都是比较老生常谈的图片优化技术,小程序的 image 组件 自带 lazy-load
懒加载支持。雪碧图技术(CSS Sprite)可以参考 w3schools 的教程。
在不得不使用大图资源的场景下,我们可以适当使用 “体验换速度” 的措施来提升渲染性能。
小程序会把已加载的静态资源缓存在本地,当短时间内再次发起请求时会直接从缓存中取资源(与浏览器行为一致)。因此,对于大图资源,我们可以先呈现高度压缩的模糊图片,同时利用一个隐藏的
节点来加载原图,待原图加载完成后再转移到真实节点上渲染。整个流程,从视觉上会感知到图片从模糊到高清的过程,但与对首屏渲染的提升效果相比,这点体验落差是可以接受的。
// banner.js
Component({
ready() {
this.originUrl = 'https://path/to/picture' // 图片源地址
this.setData({
url: compress(this.originUrl) // 加载压缩降质的图片
preloadUrl: this.originUrl // 预加载原图
})
},
methods: {
onImgLoad() {
this.setData({
url: this.originUrl // 加载原图
})
}
}
})
⑨骨架屏
一方面,我们可以从降低网络请求时延、减少关键渲染的节点数这两个角度出发,缩短完成 FMP(首次有效绘制)的时间。另一方面,我们也需要从用户感知的角度优化加载体验。
“白屏” 的加载体验对于首次访问的用户来说是难以接受的,我们可以使用尺寸稳定的骨架屏,来辅助实现真实模块占位和瞬间加载。
骨架屏目前在业界被广泛应用,首页可选择使用灰色豆腐块作为骨架屏的主元素,大致勾勒出各模块主体内容的样式布局。由于微信小程序不支持 SSR(服务端渲染),使动态渲染骨架屏的方案难以实现,因此首页的骨架屏可以通过 WXSS 样式静态渲染的。
页面主体
但这种做法的维护成本比较高,每次页面主体模块更新迭代,都需要在骨架屏组件中的对应节点同步更新(譬如某个模块的尺寸被调整)。除此之外,感官上从骨架屏到真实模块的切换是跳跃式的,这是因为骨架屏组件和页面主体节点之间的关系是整体条件互斥的,只有当页面主体数据 Ready(或渲染完毕)时才会把骨架屏组件销毁,渲染(或展示)主体内容。
为了使用户感知体验更加丝滑,我们把骨架屏元素拆分放到各个业务组件中,骨架屏元素的显示/隐藏逻辑由业务组件内部独立管理,这就可以轻松实现 “谁跑得快,谁先出来” 的并行加载效果。除此之外,骨架屏元素与业务组件共用一套 WXML 节点,且相关样式由公共的 sass
模块集中管理,业务组件只需要在适当的节点挂上 skeleton
和 skeleton__block
样式块即可,极大地降低了维护成本。
// banner.scss
.banner--skeleton {
@include skeleton;
.banner_wrapper {
@include skeleton__block;
}
}
当调用 wx.navigateTo
打开一个新的小程序页面时,小程序框架会完成这几步工作:
1. 准备新的 webview 线程环境,包括基础库的初始化;
2. 从逻辑层到视图层的初始数据通信;
3. 视图层根据逻辑层的数据,结合 WXML 片段构建出节点树(包括节点属性、事件绑定等信息),最终与 WXSS 结合完成页面渲染;
由于微信会提前开始准备 webview 线程环境,所以小程序的渲染损耗主要在后两者 数据通信 和 节点树创建/更新 的流程中。相对应的,比较有效的渲染性能优化方向就是:
①合并 setData
调用
尽可能地把多次 setData
调用合并成一次。我们除了要从编码规范上践行这个原则,还可以通过一些技术手段降低 setData
的调用频次。
②只把与界面渲染相关的数据放在 data
中
setData
传输的数据量越多,线程间通信的耗时越长,渲染速度就越慢。与视图层渲染无关的数据尽量不要放在 data
中,可以放在页面(组件)类的其他字段下。
③应用层的数据 diff
每当调用 setData
更新数据时,会引起视图层的重新渲染,小程序会结合新的 data
数据和 WXML 片段构建出新的节点树,并与当前节点树进行比较得出最终需要更新的节点(属性)。
即使小程序在底层框架层面已经对节点树更新进行了 diff,但我们依旧可以优化这次 diff 的性能。譬如,在调用 setData
时,提前确保传递的所有新数据都是有变化的,也就是针对 data 提前做一次 diff。
④去掉不必要的事件绑定
当用户事件(如 Click
、Touch
事件等)被触发时,视图层会把事件信息反馈给逻辑层,这也是一个线程间通信的过程。但,如果没有在逻辑层中绑定事件的回调函数,通信将不会被触发。
所以,尽量减少不必要的事件绑定,尤其是像 onPageScroll
这种会被频繁触发的用户事件,会使通信过程频繁发生。
⑤去掉不必要的节点属性
组件节点支持附加自定义数据 dataset
(见下面例子),当用户事件被触发时,视图层会把事件 target
和 dataset
数据传输给逻辑层。那么,当自定义数据量越大,事件通信的耗时就会越长,所以应该避免在自定义数据中设置太多数据。
Click Me!
// js
Page({
bindViewTap(e) {
console.log(e.currentTarget.dataset)
}
})
⑥适当的组件颗粒度
小程序的组件模型与 Web Components 标准中的 ShadowDOM 非常类似,每个组件都有独立的节点树,拥有各自独立的逻辑空间(包括独立的数据、setData
调用、createSelectorQuery
执行域等)。
不难得出,如果自定义组件的颗粒度太粗,组件逻辑过重,会影响节点树构建和新/旧节点树 diff 的效率,从而影响到组件内 setData
的性能。另外,如果组件内使用了 createSelectorQuery
来查找节点,过于庞大的节点树结构也会影响查找效率。
⑦事件总线,替代组件间数据绑定的通信方式
WXML 数据绑定是小程序中父组件向子组件传递动态数据的较为常见的方式。如下面例程所示:Component A
组件中的变量 a
、b
通过组件属性传递给 Component B
组件。在此过程中,不可避免地需要经历一次 Component A
组件的 setData
调用方可完成任务,这就会产生线程间的通信。“合情合理”,但,如果传递给子组件的数据只有一部分是与视图渲染有关呢?
// Component B
Component({
properties: {
propA: String,
propB: String,
},
methods: {
onLoad: function() {
this.data.propA
this.data.propB
}
}
})
推荐一种特定场景下非常便捷的做法:通过事件总线(EventBus),也就是发布/订阅模式,来完成由父向子的数据传递。其构成非常简单(例程只提供关键代码...):
一个全局的事件调度中心
class EventBus {
constructor() {
this.events = {}
}
on(key, cb) { this.events[key].push(cb) }
trigger(key, args) {
this.events[key].forEach(function (cb) {
cb.call(this, ...args)
})
}
remove() {}
}
const event = new EventBus()
事件订阅者
// 子组件
Component({
created() {
event.on('data-ready', (data) => { this.setData({ data }) })
}
})
事件发布者
// Parent
Component({
ready() {
event.trigger('data-ready', data)
}
})
子组件被创建时事先监听数据下发事件,当父组件获取到数据后触发事件把数据传递给子组件,这整个过程都是在小程序的逻辑层里同步执行,比数据绑定的方式速度更快。
但并非所有场景都适合这种做法。具有 “数据单向传递”、“展示型交互” 特性、且 一级子组件数量庞大 的场景,使用事件总线的效益将会非常高;但若是频繁 “双向数据流“ 的场景,用这种方式会导致事件交错难以维护。
⑧组件层面的 diff
我们可能会遇到这样的需求,多个组件之间位置不固定,支持随时随地灵活配置。
首页主体可被划分为若干个业务组件(如搜索框、导航栏、商品轮播等),这些业务组件的顺序是不固定的,今天是搜索框在最顶部,明天有可能变成导航栏在顶部了(夸张了...)。我们不可能针对多种顺序可能性提供多套实现,这就需要用到小程序的自定义模板 。
实现一个支持调度所有业务组件的模板,根据后台下发的模块数组按序循环渲染模板,如下面例程所示。
// search-bar.js
Component({
properties: {
floorId: Number,
},
created() {
event.on('data-ready', (comps) => {
const data = comps[this.data.floorId] // 根据楼层位置取数据
})
}
})
貌似非常轻松地完成需求,但如果组件顺序调整了,所有组件的生命周期会发生什么变化?
假设,上一次渲染的组件顺序是 ['search-bar','nav-bar','banner', 'icon-nav']
,现在需要把 nav-bar
组件去掉,调整为 ['search-bar','banner', 'icon-nav']
。经实验得出,当某个组件节点发生变化时,其前面的组件不受影响,其后面的组件都会被销毁重新挂载。原理很简单,每个组件都有各自隔离的节点树(ShadowTree
),页面 body 也是一个节点树。在调整组件顺序时,小程序框架会遍历比较新/旧节点树的差异,于是发现新节点树的 nav-bar
组件节点不见了,就认为该(树)分支下从 nav-bar
节点起发生了变化,往后节点都需要重渲染。
但实际上,这里的组件顺序是没有变化的,丢失的组件按道理不应该影响到其他组件的正常渲染。所以,我们在 setData
前先进行了新旧组件列表 diff:如果 newList
里面的组件是 oldList
的子集,且相对顺序没有发生变化,则所有组件不重新挂载。除此之外,我们还要在接口数据的相应位置填充上空数据,把该组件隐藏掉。
通过组件 diff 的手段,可以有效降低视图层的渲染压力,如果有类似场景的朋友,也可以参考这种方案。
想必没有什么会比小程序 Crash 更影响用户体验了。当小程序占用系统资源过高,就有可能会被系统销毁或被微信客户端主动回收。
应对这种尴尬场景,除了提示用户提升硬件性能之外,还可以通过一系列的优化手段降低小程序的内存损耗。
①内存预警
小程序提供了监听内存不足告警事件的 API:wx.onMemoryWarning,旨在让开发者收到告警时及时释放内存资源避免小程序 Crash。内存告警的信息收集倒是有意义的,我们可以把内存告警信息(包括页面路径、客户端版本、终端手机型号等)上报到日志系统,分析出哪些页面 Crash 率比较高,从而针对性地做优化,降低页面复杂度等等。
②回收后台页面计时器
根据双线程模型,小程序每一个页面都会独立一个 webview 线程,但逻辑层是单线程的,也就是所有的 webview 线程共享一个 JS 线程。以至于当页面切换到后台态时,仍然有可能抢占到逻辑层的资源,如没有销毁的 setInterval
、setTimeout
定时器
在页面 onHide
的时候手动把定时器清理掉,有必要时再在 onShow
阶段恢复定时器。坦白讲,区区一个定时器回调函数的执行,对于系统的影响应该是微不足道的,但不容忽视的是回调函数里的代码逻辑,如在定时器回调里持续 setData
大量数据。
③避免频发事件中的重度内存操作
我们经常会遇到这样的需求:广告曝光、图片懒加载、导航栏吸顶等等,这些都需要我们在页面滚动事件触发时实时监听元素位置或更新视图。在了解小程序的双线程模型之后不难发现,页面滚动时 onPageScroll
被频发触发,会使逻辑层和视图层发生持续通信,若这时候再调用 setData
传输大量数据,会导致内存使用率快速上升,使页面卡顿甚至 “假死”。所以,针对频发事件的监听,我们最好遵循以下原则:
onPageScroll
事件回调使用节流;setData
,或减小 setData
的数据量;④大图、长列表优化
据 小程序官方文档 描述,大图片和长列表图片在 iOS 中会引起 WKWebView 的回收,导致小程序 Crash。对于大图片资源(譬如满屏的 gif 图)来说,我们只能尽可能对图片进行降质或裁剪,当然不使用是最好的。
对于长列表,譬如瀑布流,这里提供一种思路:我们可以利用 IntersectionObserver 监听长列表内组件与视窗之间的相交状态,当组件距离视窗大于某个临界点时,销毁该组件释放内存空间,并用等尺寸的骨架图占坑;当距离小于临界点时,再取缓存数据重新加载该组件。然而当用户快速滚动长列表时,被销毁的组件可能来不及加载完,视觉上就会出现短暂的白屏。我们可以适当地调整销毁阈值,或者优化骨架图的样式来尽可能提升体验感。
小程序官方提供了一个 长列表组件,可以通过 npm
包的方式引入,有兴趣的可以尝试。
一、性能
1、图片太大会增加下载时间和内存的消耗,应根据显示区域大小合理控制图片大小
2、请求的耗时太长会让用户一直等待甚至离开,应当优化好服务器处理时间、减小回包大小,让请求快速响应
3、由于小程序运行逻辑线程与渲染线程之上,setData的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间 了解更多
4、发起网络请求总会让用户等待,可能造成不好的体验,应尽量避免多余的请求,比如对同样的请求进行缓存
5、首屏时间是指用户开始看到第一屏的内容的时间,首屏时间太长会导致用户长时间看到的都是白屏,会一直等待有意义的内容展示出来。出现这一情况,应仔细检查这个过程都有哪个操作,一般来说,可能是请求数据的时间太长,或者是一次渲染的数据太大导致渲染时间太长。
6、setData接口的调用涉及逻辑层与渲染层间的线程通过,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用
二、体验
1、我们应该合理地设置好可点击元素的响应区域大小,如果过小会导致用户很难点中,体验很差
2、文字颜色与背景色需要搭配得当,适宜的颜色对比度可以让用户更好地阅读,提升小程序的用户体验 了解更多