最近两个月,花费大量时间去调试各种页面的性能问题,解决大量卡顿问题、内存溢出问题(解决性能问题,真的是很有技术成就感的),感觉我在前端页面性能调试已经进阶成一个有经验的人了,可以总结一些常见问题与解决办法:

调试工具

  • 调试工具performan面板里,分析哪个函数用时过长,就针对优化。如果是渲染时间过长,大概率就是DOM元素太多。

chrome-devtool-performance

  • 调试工具memory面板,用于定位内存溢出问题。
    chrome-devtool-memory

  • 调试工具Rendering面板里【在右上省略号里more-tools里找】,分析页面DOM元素绘制情况。

chrome-devtool-rendering

常见问题

DOM过多

 DOM元素过多会导致渲染时间过长,类似的问题就是“如何一个表格里显示10万条数据”,解决的办法就是“虚拟滚动表格”,只把需要显示出来的渲染出来,避免有太多DOM元素。更有发现,扩展栏里收起来隐藏没显示的内容,也在不断地渲染刷新,优化的办法就是没显示出来的内容就不应该渲染更新。

DOM没有变化也触发更新

 一般来说,只要内容没变化,就可以不更新DOM。利用Rendering面板可以分析,哪些DOM明明内容没有变化,但是还是刷新渲染了,说明不合理。在使用Vue\React时经常由于开发人员搞了个对象,可能只是对象里某个字段变化了,导致的全部更新。优化时,保证只让变化的部分更新,而不是整个都更新。Vue框架更新对象时,优先使用this.$set()来更新,而不是整个对象替代。

在长数组里反复循环找对象

 开发人员反复使用循环找对应对象,用对象或者Map来优化一下就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 有一个长数组
const list = [
{id:1,name:'foo1'},
{id:2,,name:'foo2'},
//...有好多个省略
{id:1024,name:'foo1024'}
//...有好多个省略
]

function findName(id){
const obj = list.find(i=>i.id === id)
return obj.name
}

// 反复在长数组里循环寻找,会花费大量时间
const name1 = findName(100)
const name2 = findName(1024)
const name3 = findName(2400)

// 优化处理,可以把它变成对象,而不需要要数组里循环寻找
const listObj = {}
list.forEach(i=>{
listObj[i.id] = i.name
})

function betterFindName(id){
// 无需反复循环寻找
return listObj[id]
}

Vue实例里绑定复杂对象

 具体表现为proxySetter函数使用时间过长,解决办法在另一文章里有讨论:解决Vue框架下proxySetter时间过长的问题

单个函数执行时间过长

 页面最佳渲染性能是保证60帧(1000ms/60 约为16ms),所以一个函数执行时最好不要超过10ms,当一个函数执行超过50ms时就会导致执行时页面出现明显卡顿,所以在performance里超过50ms的会标红。之前优化公司项目里就发现有一个函数执行时间过长,我的做法是把里面的函数拆成几块,分别延时执行,不要都挤在一起执行,充分利用空闲时间就没那么卡顿了。浏览器是提供了一个叫requestIdelCallbak接口,浏览器会在空闲的时候调用,利用它来实现空闲时间实行,是性能优化的好接口。
 大量复杂计算尽量放到worker里执行,不要让它影响到主进程影响到渲染。canvas可以使用worker渲染,分层渲染等方式优化。

内存溢出问题

 有时候发现网页随着运行时间的增长,内存不断快速增加,一定时间后就会因为占用内存过多导致网页崩溃,这种属于内存溢出问题。一般是大对象的引用没有消除,导致无法被垃圾回收。这时使用调试工具里的memory面板,就可以定位出哪些变量占用内存比较多,一般分析一下变量,所调用的函数名等,就可以判断出是哪里导致的问题,进而优化。
 一般来说,常见导致内存溢出的问题大多是:关闭页面时没释放对应资源、所做的优化缓存在不再使用时没有清空释放等待。特别地,如果所编写的代码是以类的方式来编写的,直接搜索类名就可以确认,这类对象有没有释放完全,十分方便。
 可以按时间来定位,如下图所示:
chrome-devtool-memory-按时间分析内存
 蓝色的柱子代表还没释放的内存,限定范围后能进行定位。 鼠标移到对象之上可以看对象内容,对象旁边的窗口小图标代表当前页面用到此对象。(PS:如果看到distance为-的,说明有可能是因为在console里打印了这个对象,导致没法对其回收。)