小程序面试常见问题
本文整理了小程序面试过程中常见的问题,包括什么是分包,分包异步化,setData的流程以及开发中使用setData需要注意的事项等。
什么是分包
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 20M
- 单个分包/主包大小不能超过 2M
打包原则
- 声明
subpackages
后,将按subpackages
配置路径进行打包,subpackages
配置路径外的目录将被打包到 app(主包) 中 - app(主包)也可以有自己的 pages(即最外层的 pages 字段)
subpackage
的根目录不能是另外一个subpackage
内的子目录tabBar
页面必须在 app(主包)内
引用原则
packageA
无法 requirepackageB
JS 文件,但可以 requireapp
、自己 package 内的 JS 文件;使用分包异步化时不受此条限制packageA
无法 importpackageB
的 template,但可以 requireapp
、自己 package 内的 templatepackageA
无法使用packageB
的资源,但可以使用app
、自己 package 内的资源
什么是分包异步化
在小程序中,不同的分包对应不同的下载单元;因此,除了非独立分包可以依赖主包外,分包之间不能互相使用自定义组件或进行 require
。「分包异步化」特性将允许通过一些配置和新的接口,使部分跨分包的内容可以等待下载后异步使用,从而一定程度上解决这个限制。
setData 的流程
setData
的过程,大致可以分成几个阶段:
- 逻辑层虚拟 DOM 树的遍历和更新,触发组件生命周期和 observer 等;
- 将 data 从逻辑层传输到视图层;
- 视图层虚拟 DOM 树的更新、真实 DOM 元素的更新并触发页面渲染更新。
setData 使用注意事项
setData 应只用来进行渲染相关的数据更新。用 setData 的方式更新渲染无关的字段,会触发额外的渲染流程,或者增加传输的数据量,影响渲染耗时。
- ✅ 页面或组件的 data 字段,应用来存放和页面或组件渲染相关的数据(即直接在 wxml 中出现的字段);
- ✅ 页面或组件渲染间接相关的数据可以设置为「纯数据字段」,可以使用 setData 设置并使用 observers 监听变化;
- ✅ 页面或组件渲染无关的数据,应挂在非 data 的字段下,如
this.userData = {userId: 'xxx'}
; - ❌ 避免在 data 中包含渲染无关的业务数据;
- ❌ 避免使用 data 在页面或组件方法间进行数据共享;
- ❌ 避免滥用纯数据字段来保存可以使用非 data 字段保存的数据。
控制 setData 的频率
每次 setData 都会触发逻辑层虚拟 DOM 树的遍历和更新,也可能会导致触发一次完整的页面渲染流程。过于频繁(毫秒级)的调用 setData
,会导致以下后果:
- 逻辑层 JS 线程持续繁忙,无法正常响应用户操作的事件,也无法正常完成页面切换;
- 视图层 JS 线程持续处于忙碌状态,逻辑层 -> 视图层通信耗时上升,视图层收到消息的延时较高,渲染出现明显延迟;
- 视图层无法及时响应用户操作,用户滑动页面时感到明显卡顿,操作反馈延迟,用户操作事件无法及时传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层。
因此,开发者在调用 setData 时要注意:
- ✅ 仅在需要进行页面内容更新时调用 setData;
- ✅ 对连续的 setData 调用尽可能的进行合并;
- ❌ 避免不必要的 setData;
- ❌ 避免以过高的频率持续调用 setData,例如毫秒级的倒计时;
- ❌ 避免在 onPageScroll 回调中每次都调用 setData。
选择合适的 setData 范围
组件的 setData 只会引起当前组件和子组件的更新,可以降低虚拟 DOM 更新时的计算开销。
- ✅ 对于需要频繁更新的页面元素(例如:秒杀倒计时),可以封装为独立的组件,在组件内进行 setData 操作。必要时可以使用 CSS contain 属性限制计算布局、样式和绘制等的范围。
setData 应只传发生变化的数据
setData 的数据量会影响数据拷贝和数据通讯的耗时,增加页面更新的开销,造成页面更新延迟。
- ✅ setData 应只传入发生变化的字段;
- ✅ 建议以数据路径形式改变数组中的某一项或对象的某个属性,如
this.setData({'array[2].message': 'newVal', 'a.b.c.d': 'newVal'})
,而不是每次都更新整个对象或数组; - ❌ 不要在 setData 中偷懒一次性传所有 data:
this.setData(this.data)
。
控制后台态页面的 setData
由于小程序逻辑层是单线程运行的,后台态页面去 setData
也会抢占前台页面的运行资源,且后台态页面的的渲染用户是无法感知的,会产生浪费。在某些平台上,小程序渲染层各 WebView 也是共享同一个渲染线程,后台页面的渲染也会导致前台页面的卡顿。
- ✅ 页面切后台后的更新操作,应尽量避免,或延迟到页面
onShow
后延迟进行; - ❌ 避免在切后台后仍进行高频的 setData,例如倒计时更新。
数据通信注意事项
由于小程序的逻辑层和视图层是两个独立的运行环境、分属不同的线程或进程,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。
iOS/iPadOS/MacOS 上,数据传输是通过
evaluateJavascript
实现的,还会有额外 JS 脚本解析和执行的耗时。
数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。