头图

问题背景

最近测试丢给我一个bug:分组的编辑页面点击退出之后没有反应,一直卡着,持续了一分钟左右才退出来。
没得说,优化吧。

问题分析

这是一个分组编辑的页面,负责对设备进行分组编辑。在这个页面点击退出的时候会判断用户有没有进行编辑操作,如果有编辑操作但是没有保存,那就会拦截退出,弹窗提示用户保存。以此来避免用户误操作。
这次卡顿是因为这个分组里有7000多台设备,导致最后判断用户是否进行过编辑的计算耗时太长.看来得看看我的实现方法有什么问题了.

实现方法

我当时实现这个功能的思路是对比页面的原始数据退出时候的数据。这是个常规思路,这个思路很简单,优点就是不用考虑中间过程,只考虑两个节点的状态就行了.
我当时用了这样的一个方法来判断这两个节点的数据(这是两个由对象组成的数组)是否一致:

function hasEdit (old_data, now_data) {
    let has_edit = false;
    if (old_data.length == now_data.length) {
        let new_data_str = JSON.stringify(now_data);
        let ret = old_data.find(item => {
            let reg = new RegExp(`"id":"${item.id}"`, 'g');
            return reg.test(new_data_str);
        });
        has_edit = !!ret;
    } else {
        has_edit = true;
    }
    return has_edit;
}

我这里还美滋滋的感觉自己用了一个JSON.stringify,减少了循环,降低了时间复杂度。
但是实际上问题就出在这个JSON.stringify,从这篇文章(如何提升JSON.stringify()的性能?),我们可以看出来,其实JSON.stringify是一个很慢的操作,而且他的底层实现就是循环。这里看起来的优势并不存在。另外测试数据也证明了这一点,在用4000条数据测试时的耗时为55701ms。这也就是上面提到的卡顿的根本原因。
那么,我们换用循环来实现会不会好点呢?

优化实现

find循环实现

function hasEdit (old_data, now_data) {
    let has_edit = false;
    if (old_data.length == now_data.length) {
        let ret = old_data.find(item => {
            let result = now_data.find(now_item => {
                return now_item.id == item.id;
            })
            return !!result;
        });
        has_edit = !!ret;
    } else {
        has_edit = true;
    }
    return has_edit;
}

双层循环,两个find找到就停止循环,测了一下时间1483ms,虽然性能提升了将近40倍,但是这个时间还是大,还是有卡顿的感觉,尤其是在什么操作都没有做就退出的极端情况下更明显.看来还需要优化,但是在这个思路下好像也没有什么更好的优化方法了,看来要换个思路了.

更改实现思路

在技穷之后,我换了个思路,既然对比前后的不同性能开支太大,那就记录用户操作吧.
用户勾选或者取消勾选设备的时候判断操作的设备id是否已经被记录,如果有记录过,那就删除记录;反之,就记录下.这样退出的时候判断记录是否为空来判断是否有编辑.
这个方法看起来繁琐,但是实现起来其实很简单:

function recordChangeDevices (id) {
    let index = this.change_deivces.indexOf(id); // change_deivces已经声明过
    if (index >= 0) {
        this.change_deivces.splice(index, 1);
    } else {
        this.change_deivces.push(id);
    }
}

这样相当于把大数据量的diff计算分摊在用户每次做操作的时候.操作了一下,发现没有卡顿发生,顺便象征性的测了下时间,0ms.nice!卡顿问题成功解决.看来这个思路还不错.

小思考

还是太无知啊,对原理不了解,就认为JSON.stringify这样原生方法会优于我自己写的循环,还美滋滋的认为做了一个讨巧的操作.看来应该加深对原理性知识的认知.
另外,以后在做需求的时候也应该多考虑一下是否会出现大数据量,出现的大数据量的时候我的实现方法是否能扛得住,尤其是在业务的关键点,以免产出这种卡成翔的页面.真是惭愧啊.


aqiongbei
2k 声望281 粉丝

人生路上,你走的每一步都算数