一次小小的性能优化

239 阅读5分钟

没事自己写了一个移动端日期选择器(三排,上下滚动来选择年月日的那种),还放到了公司项目中,

一开始还是一个小小的 只要选个日期就好,

实现难点:

1:touchmove滚动,然后选中相应的日期(年月日);

2:滚动时切换年月时对应的级联变化(闰年非闰年在二月有变化,大月小月之间日期也会有变化);

3:级联变化导致视图更新后,将选中元素归位(比如原来选中31号现在切换到小月,31号不能选,需调整到30号);


由于本文不介绍实现(主要是优化)

故仅仅带过其他实现:

(滚定位置与选中元素的关联,滚动高度/单个元素高度 = 显示的列表第n项),

(级联变化的需要更新视图,监听选中元素变化即可),

(元素归位,滚动的高度不能大于容器高度-1个元素的高度),


设计渲染试图的数据如下

1.{

  yea: [{num:2018},...],

  mon:  [{num:0},...],

  dat: [{num:1},...],

}

这样得到一个比较小的数据量的数据结构;

还能更小? (mon 的 1-11 在 dat里面有,可以引用,这样索引总比新对象占的资源少)

这种结构在切换年月时,不需要重新生成渲染数据,但是需要对相应日期数据做显隐控制(比如二月没有30 31 号),


然后还得到有一种比较大的数据结构;

2:[{

  num:2018,

  list: [{num:1 , list: [{num: 0}]},...]

},...]

这种数据结构可以在生成时,控制好数量,无需做显隐控制,但是数据量非常大,比如生成80年可选的日期,会生成 大概 80*12*30 = 28800 个对象,

相比 80+12 +31 = 123 个对象来说,资源消耗是相当的大;


一开始选择了第一种数据结构;

因为一开始的目标是 渲染从某年开始往后n年的日期选择,

然后写个方法判断当年是否是闰年,

然后写个方法判断当月是否大小月,

然后两个方法一组合,就可以计算出当月有多少天,

然后和日期对象的num属性做比较就可以知道显隐关系;

后面加了一需求,需要指定日期范围,这样就导致不仅要计算日期的显隐,包括月份显隐,

然后感觉这样子写有些笨笨的(vue 等框架的数据驱动是这样子用的吗,好像有点不是);


然后切换到第二种数据结构,

不考虑初始化性能,初始化之后的逻辑确实简单了许多,因为将复杂度解耦出来了,

touchmove的时候,选中了哪年就拿哪年的数据去渲染,什么事都好办了不是(切换月份同理);

然后到了测试那就出事了,在某些机型上(没错就是苹果),多渲染几次就崩溃了;

嗯毕竟移动端都是单页面应用,并且每个页面使用的日期数据不一定一样,所以每个页面(或者重新进入页面)调用会重新生成渲染数据;


然后就想办法优化呗,改回去是不可能的了,

那就拿它俩融合下呗,


年份列表是不太可能复用的

(性能主要不是它,再多也不过就是100,嗯选个出生日期还能过百?再不行给个200?)


那就从月份开始吧,

每年都是12个月没得跑了,区别就是闰年和平年区别的了,两个数组二选一吧;


然后是日期,每个月份有 28 或者29 或者30 或者31 天;

那行, 来个四选一;

嗯嗯,其他要用的时候,就来拿索引吧

比如渲染2018 开始以及往后九年的共十年的数据;

那么 除了 2020 2024(应该没漏吧)是引用闰年的月份数组外,

其他都是引用的平年的月份数组,

然后日期的还可以优化,

四个数组里面的对象都是来自最长的那个

比如 datArr31= new Array(31).fill(0).map((item,index)={num: index+1})

那么 datArr30 = datArr31.slice(0,30),其他同理。


在实现某日前不能选取的时候,稍微麻烦一点,需要重新生成该年的渲染数据(除了日期对象,其他都不能复用,比如将某月的前几天去掉了,然后其他实例引用时,该月的前n天就米有了,年份引用月份时同理),


现在回过头来算算性能,

虽然达不到最初的那种数据结构的性能,

但胜在将复杂度解耦出来了(数据生成与业务逻辑之间解耦);


如果看过JavaScript忍者秘籍里的好像叫做函数记忆吧,

润平年的月份数组,28 29 30 31 的日期数组等可以放到生成数据的方法上(在执行时生成后绑定到方法的属性上(什么,还有给方法加属性这种操作?)),

这样各页面(或重新进入页面)时,无需重新生成这些东西;


也就是假设80年 80+ 12*2 +31 = 135 个对象(由于第一年的月份对象也不能复用 那再加上 12 = 147),

然后加上 建立索引的数组了 (80*12 =960), 差不多1200 够了吧,

相比原来 近3w 来说效果还是很显著的;