最近一段时间一直在处理公司的一个内部项目哈,因为页面卡的问题被吐槽了一个月了。因为是接手别人的项目,虽然说不是自己直接造成的。但是解决这个问题已是迫在眉睫了。
可能我们在做前端开发的时候,很少去关注内存的问题,这是因为JS有自己垃圾回收机制,如果代码不是滥用闭包,一般来说,不会出现性能问题。
先来大概说下我的项目中性能瓶颈的现象:项目是一个基于electron+vue的一个类似微信桌面端的项目,因为是公司项目就不截图了,想象是微信就行了。
当我们切换左侧联系人列表的时候,发现内存不断的上升,而且很难降下来,很典型的内存泄露了。
时间一长,内存吃完了,程序直接崩了。。。
好吧,解决问题吧,知道有内存泄露,我们一般会找项目中的代码是不是有没有及时释放的闭包局部变量。
但是,效果不明显。
我的解决方案
1、查看当前页面的dom节点数。
一般来说在3000左右是为正常吧。这个如果查看呢?
很简单,我们只需要在浏览器控制台输入:
$$("*")
即可!
当我在我的项目中输入的时候,节点达到了10000+了,好家伙,问题找到了,原来是dom渲染太多了?
这应该是一个瓶颈吧,接下来不是去掉多余的dom了,我发现在渲染列表的时候,一些很多的原点是没有必要时时渲染的,还有一些dom是通过v-show来隐藏起来的。
慢慢的把这些问题解决了,dom节点降下来了。内存是得到了很好的控制。
你们以为这就完了吗?
但是随着列表的切换,内存还是会上升,只是没那么明显了。
What??
你们说气人不?没办法,还得继续优化了。
这时候,我们要结合我的第一种方案,形成第二种方案。
2、优化渲染列表本身。
一般来说,我们列表的数据是通过后端或者本地数据库获取到的一个数组对象,而数组的每一项一般都是一个Json对象。我们是直接将这个数据对象挂载到组件的data对象或者vuex中去。
不管是哪种方案,在读过vue源码后,知道他是存在一问题的。
首先,我们的组件的data对象下数据,vue为了双向数据绑定,会给每一个属性添加get和set函数。也就是说。一个数据对象所有的属性都会添加一次get和set,但是如果说我们的列表中的数据不需要数据响应,那这个get和set是没有意义的。
好了,有这个思路,我们应该要想办法不让vue去给这个列表数据添加get和set。那该怎么做呢?
要么说阅读源码很重要呢?因为我之前看过数据响应的相关源码哈,我就直接带大家看一眼吧。
在defineReactive方法中,它是专门用来定义get和set的,我们可以看到有一个情况他是不进行get和set的,当对象的configable为false的时候。
一般来说,一个对象的configurable属性为false的时候。
好像看到希望了
在ES5中,Object对象下有一个方法可以!!!
Object.freeze ,它可以将一个对象冻结起来。
当我们用Object.freeze将对象冻结的时候,我们再来看效果
结果一目了然了吧。
好了,接下来我们将我们要渲染的列表用Object.freeze来冻结。再来看效果。
果不其然,内存下来了,正常了。。。哈哈哈
问题来了,当我们把对象冻结后,如果我们想去修改数组中的某一项的数据的时候,这时候我们是无法直接修改的。这时候,我们需要先将冻结后的数组拿到,然后通过[...list];进行解冻。然后修改,然后将新的数组重新冻结再去更新data或者 vuex! 完美解决。
技术总结:
在排查问题的同时,我先考虑了本地数据库的性能问题,排除后再去排查ui的问题。
我们在vue项目开发的时候,一定要注意我们看不到的页面不要渲染无效的dom结构,要想提高列表的渲染性能,Object.freeze能大大帮助们提高页面性能。
从排查问题,到解决问题,过程是痛苦的,但是当问题被解决的时候,被用户肯定的时候,这些都值了。。