利用Chrome优化页面

背景

咱们公司的内部系统中,有一个配置组件(如图所示),功能为实现左边勾选节点,右边显示所选节点的交互列表(这里和下面都作了字段脱敏处理,因此看起来没什么意义,但功能是同样的)。node

卡顿例子

上线后,一直风平浪静,可是有一天,测试数据库被导入了一批1.8万的数据,而后致使这个组件的所在页面出现了下面的性能问题。git

  • 进入组件所在页面,初始化严重卡顿;
  • 勾选一个节点要等上十几二十秒;
  • 全选或取消全选甚至等上一分多钟;

经过Chrome Performance 定位性能问题

录制

  1. 打开Chrome控制台,点击performance标签(下图所示),备用;
  2. 复现会引发页面性能问题的动做;
  3. 点击左上角的录制按钮,开始录制;
  4. 录制时间取决于你的页面须要监控的时间段
  5. 中止录制,会出现一个十分复杂的页面。

定位耗时代码

这里录制了列表点击全选时的时间段(以下图所示)。首先这里作个说明:performance能够分析内存、CPU、FPS等好多参数,咱们这里主要是页面等待过久了,而这个取决于FPS(页面每秒帧数,fps < 24 会让用户感受到卡顿,由于人眼的识别主要是24帧)。github

咱们按着图上序号顺序解释:chrome

  1. FPS区域出现了红条,意味着帧数已经降低到影响用户体验的程度,chrome已经帮你标注了这块是有问题;
  2. Main区域展现的是火焰图,也就是函数调用的堆栈(火焰图,能够简单理解,x轴表示时间,y轴表示调用的函数,函数中还包含依次调用的函数,y轴只占用x轴的一个时间维度),点击灰色条会出现图中3的红色区域。
  3. Summary区域显示了Warning,长时间任务耗时了1.6min,其中脚本Scripting占绝大部分耗时。

这时,咱们已经知道有一个耗时很长的任务了,咱们继续深究究竟是哪一些代码形成的:数据库

  1. 咱们在函数栈从上往下找(以下图所示),找到该函数占用的时间(Self Time)很大时,问题就出来这里了,Chrome还标出是哪一个文件出来。

  1. 点击耗时文件,Chrome会为咱们标出耗时长的代码(下图所示)

分析与优化

经过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

  1. 首先,数据data包含三个字段moduleIdfatherModuleIdmoduleName,其中moduleId是模块ID,fatherModuleId是模块上一级的ID。
  2. 其次,经过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是个很好的定位问题利器,方便快速找到问题代码;
  • 平时写代码时,特别循环递归,若是考虑日后会有数据大的状况,就尽可能用少的循环去完成功能;
  • ES6的新数据类型set在查找方面,数据量很大时性能大幅高于数组,推荐使用。

参考

使用Chrome分析页面性能问题

如何使用 Set 来提升代码的性能

相关文章
相关标签/搜索