性能优化是任何应用开发中的重要组成部分,尤其是在移动环境中。对于微信小程序而言,随着用户量的增加和应用功能的丰富,性能优化显得尤为关键。良好的性能不仅提升用户体验,还能增加用户留存率和应用的使用频率。我们将探讨如何在微信小程序中进行性能优化,涵盖从首屏加载、分包、网络请求到渲染性能等多个方面。
- 网络请求:合并请求,减少HTTP请求次数;使用缓存策略减少重复请求。
- 布局与样式:避免使用复杂的CSS选择器;合理使用Flex布局减少重排重绘。
- 事件处理:移除不再需要的事件监听器,防止内存泄漏。
- 组件懒加载、按需加载
一、优化首屏加载
1. 减少初始包的体积
每个小程序都有一个加载包,包含了代码及依赖。为了加快加载速度,我们应尽量减少包的体积。
-
懒加载组件:在小程序中使用
Component
时,可以选择懒加载。只有在组件实际需要时再加载。Component({properties: {show: {type: Boolean,value: false}},observers: {'show': function (newVal) {if (newVal) {// 只在需要展示时加载相关资源require('./path/to/your/component.js');}}} });
-
拆分代码:把代码拆分成多个小模块,按需加载。
合理地分割代码模块,不仅可以使代码结构更加清晰,还能够提高加载效率。只加载用户当前需要的部分,避免一次性加载整个应用的所有代码。// 示例:按需加载 const pages = {home: () => import('./pages/home'),about: () => import('./pages/about') };Page({onLoad: function() {// 根据需要动态加载页面pages.home().then(module => {module.default.loadContent();});} });
-
资源懒加载:按需加载图片和其他资源,减少初始加载时间。
<image src="" mode="widthFix" data-src="{{item.image}}" bindload="imageLoad"/>// JS部分 imageLoad(e) {e.currentTarget.src = e.currentTarget.dataset.src; }
-
资源复用:合理利用已有资源减少重复加载
对于那些多次使用的资源,如图标、背景等,我们可以将其作为公共资源存储起来,避免在每次加载页面时重复下载。
// 示例:将公共图标作为公共资源 const icons = ['icon1.png', 'icon2.png'];function preloadIcons() {icons.forEach(icon => {const img = new Image();img.src = icon;}); }preloadIcons();
2. 资源压缩与图片优化
- 图片格式与大小:使用合适的格式,如 JPEG、PNG 或 WEBP。尽可能减小图片尺寸,以减少加载时间。借助工具压缩图片,减少图片大小,同时保持较高质量。
- 使用云存储:将图片存储在云端,利用 CDN 加速加载。
- 代码压缩和混淆
利用构建工具(如webpack)进行代码压缩和混淆,减小包体积,提高加载速度。// webpack.config.js module.exports = {mode: 'production',optimization: {minimize: true,}, };
3. 优化 CSS 和 JavaScript
- 减少 CSS 选择器的复杂性:复杂的选择器会增加渲染时间,建议使用简短的类名和 ID。
- 合并 CSS 文件:将多个 CSS 文件合并成一个,减少 HTTP 请求次数。
- 使用 CSS3 动画:使用 CSS3 动画替代 JavaScript 动画,控制性能和流畅度。
二、网络请求优化
网络请求是小程序性能的另一个关键因素,优化网络请求可以显著提升用户体验。
1. 缓存机制
使用缓存机制可以减少数据重复请求,加速数据的获取。
- 使用 wx.setStorageSync 和 wx.getStorageSync:在获取数据后将其存储到本地,以后请求前先检查本地是否已有缓存。
const fetchData = async () => {const cachedData = wx.getStorageSync('myData');if (cachedData) {return cachedData; // 使用缓存}const response = await wx.request({/*...*/});wx.setStorageSync('myData', response.data);return response.data;
};
2. 限制请求数量
避免在短时间内发送过多请求,可以合并请求或使用防抖和节流技术。
-
防抖:
let debounceTimer; function debounce(func, delay) {return function (...args) {clearTimeout(debounceTimer);debounceTimer = setTimeout(() => func.apply(this, args), delay);}; }// 使用示例 const fetchDataDebounced = debounce(fetchData, 300);
-
节流:
function throttle(func, limit) {let lastFunc;let lastRan;return function () {const context = this;const args = arguments;if (!lastRan) {func.apply(context, args);lastRan = Date.now();} else {clearTimeout(lastFunc);lastFunc = setTimeout(() => {if (Date.now() - lastRan >= limit) {func.apply(context, args);lastRan = Date.now();}}, limit - (Date.now() - lastRan));}}; }
3. 分批加载:避免一次性加载大量数据
当需要加载大量数据时,可以采用分批加载的方式,每次只加载一部分数据,待这部分数据处理完毕后再加载下一批。
// 示例:分批加载数据
function fetchBatchData(page) {wx.request({url: `https://api.wwww.com/data?page=${page}`,success: function(res) {const newData = res.data;// 处理数据this.setData({items: this.data.items.concat(newData)});}});
}fetchBatchData(1);
4. 智能请求:根据网络状况调整请求策略
根据用户的网络环境动态调整请求策略,可以进一步提高数据加载的速度和成功率。
// 示例:根据网络状况调整请求策略
wx.getNetworkType({success: function(res) {const networkType = res.networkType;if (networkType === '2g') {// 在2G网络环境下减少请求次数fetchBatchData(1, 5); // 一次性请求5页数据} else {// 其他网络环境下正常请求fetchBatchData(1);}}
});
三、优化页面渲染性能
页面渲染效率直接影响用户体验,优化渲染性能是非常必要的。
1. 启动速度优化
- 减少全局数据初始化
- 使用分包加载,将不常用页面分割到子包中
2. 使用 wx:if 和 wx:for
适当使用 wx:if 和 wx:for(使用wx:key
优化列表渲染) 避免过多的视图层重绘。尽量使用 wx:show 替代 wx:if,因为 wx:if 会在条件变化时销毁和重新创建节点。
3. 合理使用数据绑定
在数据大规模更新时,避免一次性改变全部数据,使用 this.setData() 时要尽量精简,减少触发视图的重复渲染。
this.setData({'todos[0].completed': true // 只更新部分数据
});
4. 使用组件化
将页面拆分成多个小组件,每个组件负责各自的状态和渲染,这样可以有效地减少失去控制的渲染过程,增加性能。同时也提高了代码的可维护性。
5. 避免长任务
对长时间运行的任务进行拆分,避免在主线程中阻塞 UI 更新。可以使用 Promise 和 setTimeout 进行分步执行,确保 UI 不被冻结。
6. 内存管理
- 监听页面生命周期,及时释放不再使用的资源
- 使用WeakRef避免循环引用问题
onUnload() {// 清理定时器、监听器等this.timer && clearInterval(this.timer);
}
7. 削减冗余:识别与移除无用代码
删除冗余代码就像是给程序减肥,让它变得更加苗条。那些不再使用的函数、未引用的变量或者废弃的样式都应该被清理掉,这样才能保证小程序运行得更快。
// 示例:删除未使用的变量
let unusedVar = 'I will be removed'; // 不再使用此变量function cleanUpCode() {// 删除未使用的变量delete unusedVar; // 实际开发中应使用工具或手动检查
}cleanUpCode();
8. 异步处理:合理使用异步任务不阻塞主线程
合理使用异步任务可以避免阻塞主线程,从而提高程序的执行效率。
// 示例:使用Promise异步加载数据
function fetchDataAsync() {return new Promise((resolve, reject) => {wx.request({url: 'https://api.wwww.com/data',success: resolve,fail: reject});});
}fetchDataAsync().then(res => {this.setData({items: res.data});
});
9. 动画优化:流畅过渡增强交互感
通过优化动画效果,可以使用户界面更加流畅,增强用户的交互体验。
// 示例:优化动画效果
Page({data: {animate: false},startAnimation: function() {this.setData({animate: true});setTimeout(() => {this.setData({animate: false});}, 1000);}
});
四、减少小程序的启动时间
1. 初始页面优化
在小程序启动时载入的页面要尽量简化,减少初始页面的内容,延迟加载不必要的内容。
2. 使用 wx.navigateTo 与 wx.redirectTo
合理使用页面跳转方式,避免使用 wx.redirectTo 后缀跳转过多的页面,而导致栈溢出,减慢响应速度。
五、工具与监控
1. 性能监控工具
使用微信开发者工具中的性能分析工具来监控小程序的性能,查看每个调用的时间,找出性能瓶颈。
2. 代码性能分析
使用一些 JavaScript 的代码静态分析工具(如 ESLint, Prettier)来确保代码的高效性,及时优化掉低效代码。
六、优化微信小程序超包问题
1. 分包
分包部分规则:
1、子包最多100个
2、tabBar页面只能在主包中
3、子包内不能再分包
4、子包内的资源不能互相引用(可使用分包异步化解决),但可使用主包内的资源
5、整个小程序所有分包大小不超过 20M,单个分包/主包大小不能超过 2M
小程序开发者工具的代码依赖分析可以查看包体积情况
分包规则
// 在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,会把对应分包自动下载下来,下载完成后再进行展示,此时终端界面会有等待提示。
{// pages 主包(默认启动页面及 TabBar 页面)"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages{// 页面上访问路径 /pages/index/index"path": "pages/index/index","style": {"navigationBarTitleText": "首页",// custom即取消默认的原生导航栏"navigationStyle": "custom","navigationBarTextStyle": "white"// "enablePullDownRefresh":false, //是否允许底部下拉刷新,可在页面配合 onPullDownRefresh生命周期一起使用}},{"path": "pages/my/my","style": {"navigationBarTitleText": "我的","navigationStyle": "custom","navigationBarTextStyle": "white"}},{"path": "pages/cart/cart","style": {"navigationBarTitleText": "购物车"}},{"path": "pages/404/404","style": {"navigationBarTitleText": "页面未找到","enablePullDownRefresh": false}}],"globalStyle": {"navigationBarTextStyle": "black","navigationBarTitleText": "uni-app","navigationBarBackgroundColor": "#F8F8F8","backgroundColor": "#F8F8F8"},// 组件自动引入规则// easycom方式引入组件不是全局引入,而是局部引入。例如在H5端只有加载相应页面才会加载使用的组件"easycom": {// 是否开启自动扫描"autoscan": true,"custom": {// uni-ui 规则如下配置 uni扩展组件"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",//以 Xtx 开头的组件,匹配components目录内的vue文件(需要重启服务器)"^Xtx(.*)": "@/components/Xtx$1.vue"// 匹配node_modules内的vue文件,如匹配 uview-ui 包的组件// "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"}},// 底部"tabBar": {"color": "#333","selectedColor": "#27ba9b","backgroundColor": "#fff","borderStyle": "white","list": [{"text": "首页",// pagePath要与上面的pages一致"pagePath": "pages/index/index","iconPath": "static/tabs/home_default.png","selectedIconPath": "static/tabs/home_selected.png"},{"text": "购物车","pagePath": "pages/cart/cart","iconPath": "static/tabs/cart_default.png","selectedIconPath": "static/tabs/cart_selected.png"},{"text": "我的","pagePath": "pages/my/my","iconPath": "static/tabs/user_default.png","selectedIconPath": "static/tabs/user_selected.png"}// {// "text": "SKU",// "pagePath": "pages/sku/sku"// }]},// 分包页面(需手动创建subPackages文件夹),按需加载"subPackages": [{// 子包的根目录"root": "pagesMember",// 页面路径和窗口表现,和上面 pages 一样// 访问的完整路径 如:/pagesMember/settings/settings"pages": [{"path": "settings/settings","style": {"navigationBarTitleText": "设置"}},{"path": "profile/profile","style": {"navigationBarTitleText": "个人信息",// 取消默认的导航栏"navigationStyle": "custom","navigationBarTextStyle": "white"}},{"path": "address/address","style": {"navigationBarTitleText": "地址管理"}},{"path": "address-form/address-form","style": {"navigationBarTitleText": "新建地址"}}]},{"root": "pagesOrder","pages": [{"path": "create/create","style": {"navigationBarTitleText": "结算"}},{"path": "detail/detail","style": {"navigationBarTitleText": "订单详情","navigationStyle": "custom"}},{"path": "payment/payment","style": {"navigationBarTitleText": "支付结果"}},{"path": "list/list","style": {"navigationBarTitleText": "订单列表"}}]}],// 预下载分包,分包预下载规则"preloadRule": {// 进入 我的 页面,预下载分包"pages/my/my": {"network": "all",// 分包 root的值"packages": ["pagesMember"]},"pages/cart/cart": {"network": "all","packages": ["pagesOrder"]}},"condition": { //模式配置,仅开发期间生效"current": 0, //当前激活的模式(list 的索引项)"list": [{"name": "", //模式名称"path": "", //启动页面,必选"query": "" //启动参数,在页面的onLoad函数里面得到}]}
}