likes
comments
collection
share

高性能javaScript + 小程序性能优化建议

作者站长头像
站长
· 阅读数 16

一、高性能javascript

为什么优化是必须的?

尽管浏览器一直在不断的完善垃圾回收机制,追求更好的运行性能,但是大多数浏览器仍然在使用javascript解释器来执行代码。解释性代码,天生没有编译性代码快,因为解释性代码必须经历把代码转化成计算机指令的过程。无论解释器怎么优化和多么智能,它总是会带来一些性能损耗。编译性语言在编译的过程中会对编译结果进行优化,通常在其他语言中,由编译器处理的优化,在js中却要求开发人员来完成。

数据存储

数据的存储有4种方式:字面量,变量,数组项,对象成员。他们有着各自的性能特点:

  • 访问字面量和局部变量的速度最快,相反,访问数组元素和对象成员相对较慢。
    • 字面量:‘1’, 2, true, null, undefined, [1,2,3,4], {a:1, b:2 };
    • 变量:let name = 'joy';
    • 数组项:const max = [1,2,3,4][3];
    • 对象成员:const a = {a:1, b:2}['a'];
  • 由于局部变量存在于作用域链的起始位置,因此访问局部变量比访问跨作用域变量更快。变量在作用域链的位置越深,访问所需时间就越长,由于全局变量在作用域链的最末端,因此访问速度也是最慢的。

高性能javaScript + 小程序性能优化建议

  • 嵌套的对象成员会明显的影响性能,尽量少用;
  • 属性或方法在原型链中的的位置越深,访问它的的速度也越慢;
  • 最佳实践:通常来说,你可以把常用的对象成员,数组元素,跨域变量保存在局部变量中来完善javascript性能,因为局部变量访问速度更快。

//bad
const app = getApp();
function fn(){
  if(app.global.list.length>0){
    const last = app.global.list[app.global.list.length-1];
    ....
  }
}

//nice
function fn(){
  const { list } = app.global;
  const len = list.length;
  if(len>0){
    const last = list[len-1];
    ....
  }
}//bad
const app = getApp();
function fn(){
  if(app.global.list.length>0){
    const last = app.global.list[app.global.list.length-1];
    ....
  }
}

//nice
function fn(){
  const { list } = app.global;
  const len = list.length;
  if(len>0){
    const last = list[len-1];
    ....
  }
}

DOM编程

  • DOM操作天生就慢,所以一要尽量减少对DOM的访问,尽可能在js端处理逻辑;
  • 如果需要多次访问某个DOM节点,请使用局部变量来存储它的引用;
  • 批量处理DOM操作;
  • 动画中使用绝对定位,绝对定位的元素脱离文档流;
  • 减少:hover伪类的使用;
  • 使用事件委托;

流程控制

  • for,while,do-while循环的性能相当,没有明显的孰快孰慢。此外数组的函数遍历方法总是要比以上三者要慢一些,对每个数组项调用外部方法所带来的的开销是速度慢的主要原因;
  • 避免使用for-in循环除非遍历一个属性数量未知的对象,因为循环体每一次运行时,key变量都会被赋值一次,而且返回的属性还包含从原型链中继承而来的属性。

for(let key in object){...}

  • 改善循环性能的最佳方式是减少每次迭代定位运算量和减少循环迭代次数。
  • switch整理性能优于if-else,判断条件越多的时候越倾向于使用switch;
  • if-else的优化方法是:确保最可能出现的条件放到前面;

编程实践

  • 避免双重求值:js允许你在程序中提取一个包含代码字符串,然后动态的执行它,以下四种方式可以实现:
// bad !!!!
const n1 = 5;
const n2 = 6;
eval("n1 + n2");
const sum = new Function("n1", "n2", "return n1+n2");
setTimeout("sum = n1+n2", 100);
setInterval("sum = ni+n2", 100);// bad !!!!
const n1 = 5;
const n2 = 6;
eval("n1 + n2");
const sum = new Function("n1", "n2", "return n1+n2");
setTimeout("sum = n1+n2", 100);
setInterval("sum = ni+n2", 100);
  • 使用Object和Array直接量
// bad 
const obj = new Object();
obj.name = 'li lei';
obj.age = 12;
obj.flag = true;

// nice!
const obj = {
  name: 'li lei',
  age: 12,
  flag: true
}// bad 
const obj = new Object();
obj.name = 'li lei';
obj.age = 12;
obj.flag = true;

// nice!
const obj = {
  name: 'li lei',
  age: 12,
  flag: true
}
  • 避免重复工作:别做无关紧要的工作,别重复做已经完成工作;
  • 尽量使用javascript提供的原生方法。

小程序官方性能优化建议

启动耗时

  1. 利用「按需注入」和「用时注入」的特性,可以优化代码注入环节的耗时和内存占用,2.11.1以上。

{ "lazyCodeLoading": "requiredComponents" }

  • 启用按需注入后,小程序仅注入当前访问页面所需的自定义组件和页面代码。未访问的页面、当前页面未声明的自定义组件不会被加载和初始化,对应代码文件将不被执行。请开发者修改配置后务必确认小程序的表现正常
  • 启用按需注入后,页面 JSON 配置中定义的所有组件和 app.json 中 usingComponents 配置的全局自定义组件,都会被视为页面的依赖并进行注入和加载。建议开发者及时移除 JSON 中未使用自定义组件的声明,并尽量避免在全局声明使用率低的自定义组件,否则可能会影响按需注入的效果。
  • 插件包和扩展库目前暂不支持按需注入。如果需要实现插件按需加载,可以考虑将插件置于一个分包,并通过「分包异步化」的形式异步引入
  • 用时注入:占位组件;

2.启动过程中,减少同步API的调用

在小程序启动流程中,会注入开发者代码并顺序同步执行 App.onLaunch, App.onShow, Page.onLoad, Page.onShow。

在小程序初始化代码(Page,App 定义之外的内容)和上述启动相关的几个生命周期中,应尽量减少或不调用同步 API

    • 由于 getSystemInfo 接口里承载了过多内容,单次调用可能比较久。如非必要,建议开发者对调用结果进行缓存,避免重复调用。
    • 启动过程中过多的读写存储,也会显著影响小程序代码注入的耗时。

3.避免启动过程中进行复杂运算。

4.使用初始渲染提高首屏出现的时间:developers.weixin.qq.com/miniprogram…

运行时性能

  1. 合理使用setData
    • data中应该只包含和渲染相关的数据,与渲染无关的纯数据字段不应该放在data中;
    • this.pageNo = 1; this.pageSize = 10;
    • 控制setData的频率。每次 setData 都会触发逻辑层虚拟 DOM 树的遍历和更新,也可能会导致触发一次完整的页面渲染流程。
    • 选择合适的setData范围:组件的 setData 只会引起当前组件和子组件的更新,可以降低虚拟 DOM 更新时的计算开销。比如秒杀的倒计时,可以单独封装在一个组件内。
    • 建议以数据路径形式改变数组中的某一项或对象的某个属性,
    • this.setData({'array[2].message': 'newVal', 'a.b.c.d': 'newVal'})
  1. 适当监听页面或组件的scroll组件
    • ✅ 非必要不监听 scroll 事件;
    • ✅ 在实现与滚动相关的动画时,优先考虑滚动驱动动画(仅 )或 WXS 响应事件
    • ❌ 不需要监听事件时,Page 构造时应不传入 onPageScroll 函数,而不是留空函数;
    • ❌ 避免在 scroll 事件监听函数中执行复杂逻辑;
    • ❌ 避免在 scroll 事件监听中频繁调用 setData 或同步 API。
    • ❌ 避免在onPageScroll中,持续查询节点信息 SelectQuery 来判断元素是否可见。

3.控制WXML节点数量和层级

    • 一个太大的 WXML 节点树会增加内存的使用,样式重排时间也会更长,影响体验。
    • ✅ 建议一个页面 WXML 节点数量应少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个。

4.控制在page构造时,传入自定义数据量

// ❌ 使用复杂对象作为自定义数据
Page({
  onLoad() { }
  bigData: { /* A complex object */ },
  longList: [ /* A long complex array*/ ]
})

// ✅ 运行时手动赋值到 this。开发者可以根据需要选择进行深拷贝、浅拷贝或不拷贝。
Page({
  onLoad() {
    this.bigData = { /* A complex object */ },
    this.longList = [ /* A long complex array*/ ]
  }
})// ❌ 使用复杂对象作为自定义数据
Page({
  onLoad() { }
  bigData: { /* A complex object */ },
  longList: [ /* A long complex array*/ ]
})

// ✅ 运行时手动赋值到 this。开发者可以根据需要选择进行深拷贝、浅拷贝或不拷贝。
Page({
  onLoad() {
    this.bigData = { /* A complex object */ },
    this.longList = [ /* A long complex array*/ ]
  }
})

5.避免滥用image的widthFix和heightFix

widthFix/heightFix 模式会在图片加载完成后,动态改变图片的高度或宽度。图片高度或宽度的动态改变,可能会引起页面内大范围的布局重排,导致页面发生抖动,并造成卡顿。

对于页面的背景图或 banner 图,应尽量预先指定图片的尺寸,避免图片加载完成后再进行二次的尺寸调整。