那么结合我的业务代码,就分析出来问题出现在语句3当中。
<div class="card-content"> <div class="block" v-for="item in cardData" :key="item.secName"> <div class="sub-title">{{item.secName}}</div> <div class="group"> <span @click="cardClick(index + item.startIndex)" class="item" :class="getItemClass(index + item.startIndex)" v-for="(subItem, index) in item.secTids" :key="subItem">{{index + item.startIndex + 1}}</span> </div> </div> </div>
getItemClass (index) { const ret = {} // 如果是做对的题目,但并不是当前选中 ret['item_true'] = this.questions[index]...... // 如果是做对的题目,并且是当前选中 ret['item_true_active'] = this.questions[index]...... // 如果是做错的题目,但并不是当前选中 ret['item_false'] = this.questions[index]...... // 如果是做错的题目,并且是当前选中 ret['item_false_active'] = this.questions[index]...... // 如果是未做的题目,但不是当前选中 ret['item_undo'] = this.questions[index]...... // 如果是未做的题目,并且是当前选中 ret['item_undo_active'] = this.questions[index]...... return ret },
首先 cardData
是一个分组数据,循环里面套循环,假设有 10 个章节, 每个章节有 200 道题目,那么其实会执行 2000 次 getItemClass 函数,getItemClass 内部会有 6 次对 questions 进行求值,每次都会走到 dependArray,每次执行 dependArray 都会循环 2000 次,所以粗略估计 2000 * 6 * 2000 = 2400 万次,如果假设一次执行的语句是 4 条,那么也会执行接近一亿次的语句,性能自然是原地爆炸!
既然从源头分析出了原因,那么就要找出方法从源头上去解决。
拆分组件
很多人理解拆分组件是为了复用,当然作用不止是这些,拆分组件更多的是为了可维护性,可以更语义化,在同事看到你的组件名的时候,大概能猜出里面的功能。而我这里拆分组件,是为了隔离无关的响应式数据造成的组件渲染。从上图可以看出,只要任何一个响应式数据改变,Paper 都会重新渲染,比如我点击收藏按钮,Paper 组件会重新渲染,按道理只要收藏按钮这个 DOM 重新渲染即可。
在嵌套循环中,不要用函数
性能出现问题的原因是在于我用了 getItemClass 去计算每一个小圆圈的样式,而且在函数里面还对 questions 进行了求值,这样时间复杂度从 O(n²) 变成了 O(n³)(由于源码的 dependArray也会循环)。最后的解决方案,我是弃用了 getItemClass 这个函数,直接更改了 cardData 的 tids 的数据结构,变成了 tInfo,也就是在构造数据的时候,计算好样式。