咱们公司的内部系统中,有一个配置组件(如图所示),功能为实现左边勾选节点,右边显示所选节点的交互列表(这里和下面都作了字段脱敏处理,因此看起来没什么意义,但功能是同样的)。node
上线后,一直风平浪静,可是有一天,测试数据库被导入了一批1.8万的数据,而后致使这个组件的所在页面出现了下面的性能问题。git
performance
标签(下图所示),备用;这里录制了列表点击全选时的时间段(以下图所示)。首先这里作个说明:performance
能够分析内存、CPU、FPS等好多参数,咱们这里主要是页面等待过久了,而这个取决于FPS
(页面每秒帧数,fps < 24 会让用户感受到卡顿,由于人眼的识别主要是24帧)。github
咱们按着图上序号顺序解释:chrome
FPS
区域出现了红条,意味着帧数已经降低到影响用户体验的程度,chrome已经帮你标注了这块是有问题;Main
区域展现的是火焰图,也就是函数调用的堆栈(火焰图,能够简单理解,x轴表示时间,y轴表示调用的函数,函数中还包含依次调用的函数,y轴只占用x轴的一个时间维度),点击灰色条会出现图中3的红色区域。Summary
区域显示了Warning
,长时间任务耗时了1.6min
,其中脚本Scripting
占绝大部分耗时。这时,咱们已经知道有一个耗时很长的任务了,咱们继续深究究竟是哪一些代码形成的:数据库
经过Chrome,咱们已经定位出问题代码了,接下来就是分析问题的缘由了。数组
这里列出有性能问题的代码块:bash
...
computed: {
treeData () {
const tree = this.formmatData(this.data);
return tree;
},
...
},
methods: {
formmatData (origin, rootId = 0, result = {}) {
// 1.找出根节点数组
const rootNode = origin.filter(node => node.fatherModuleId === rootId);
if (rootNode.length > 0) {
// 2.对根节点数组进行遍历赋值,并放到传入result的children中
result.children = rootNode.map(node => ({
title: node.moduleName,
id: node.moduleId,
checked: origin.filter(n => n.fatherModuleId === node.moduleId).length < 1 && this.checked.includes(node.moduleId),
expand: true,
disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
}));
//3. 遍历根节点数组,递归调用该方法找下一级的节点
result.children.forEach((child) => {
this.formmatData(origin, child.id, child);
});
}
// 4.返回当前的result的孩子
return result.children;
},
...
}
复制代码
其中this.data
数据格式为:函数
[
{
"moduleId": 1,
"moduleName": "节点1",
"fatherModuleId": 0
},
{
"moduleId": 2,
"moduleName": "节点2",
"fatherModuleId": 0
},
{
"moduleId": 3,
"moduleName": "节点3",
"fatherModuleId": 0
},
...
]
复制代码
这里先说明一下,该段代码作了什么事情吧:post
data
包含三个字段moduleId
、fatherModuleId
和moduleName
,其中moduleId
是模块ID,fatherModuleId
是模块上一级的ID。fromData
方法计算出treeData
,而formmatData
作的就是递归调用,构造出树状数据。可是,就是由于里面出现了递归,循环的嵌套致使了性能问题,经过结合业务,优化作了如下操做:性能
set
来优化查找优化后的代码:
computed: {
checkedSet () {
return new Set(this.checked);
},
...
}
methods: {
formmatData (origin, rootId = 0, result = {}) {
const otherNode = [];
result.children = [];
// 1. 整合本来代码的过滤和遍历赋值,减小一次循环
for (const node of origin) {
if (node.fatherModuleId === rootId) {
result.children.push({
title: node.moduleName,
id: node.moduleId,
checked: this.checkedSet.has(node.moduleId), // 利用`set`来优化查找
expand: true,
disableCheckbox: !this.editable || this.disabledList.includes(node.moduleId),
});
} else {
otherNode.push(node);
}
}
// 2.在剩下的节点去查找子节点,而不是重复在所有数据中查找,由于当前的节点数组已经找到
if (otherNode.length > 0) {
result.children.forEach((child) => {
if (child.checked) {
// 3.利用find来代替filter
child.checked = !otherNode.find(n => n.fatherModuleId === child.id);
}
this.formmatData(otherNode, child.id, child);
});
}
return result.children;
},
...
}
复制代码
通过一番定位耗时问题,优化代码逻辑后,先后时间对好比下:
Chrome Performance
是个很好的定位问题利器,方便快速找到问题代码;set
在查找方面,数据量很大时性能大幅高于数组,推荐使用。