在项目开发进程中,遇到这样一个问题:在 form 表单页面中,其 fields(字段)会依据其中一个下拉选项的类型值来显示不同内容。开发完成后,虽然预期功能得以实现,然而在切换下拉类型时,页面加载速度却变得较为迟缓。本文将对此次性能优化的过程予以记录。
问题原因分析及解决
1. 是否为接口慢?
当下拉值发生改变后,确实会触发接口请求。不过,经过查看,接口的响应速度非常快,处于毫秒级别,因此可以排除接口响应缓慢这一猜想。
2. 页面输入项过多从而导致渲染速度慢?
虽然页面要输入的fields很多,但是在加新需求之前一直这样,以前加载也没有这么慢,但是为了排除还是做了一点优化
- 使用
v-show
代替v-if
。因为这种场景是需要频繁切换的,v-show
是基于CSS的display:none
实现的,可以减少不必要的DOM操作,性能开销比较小。---用这种方法效果并没有得到很明显的提升,还得继续优化。 - 页面加上
v-loading
。优化用户体验,不用切换完后卡着不动。---这样虽然不卡了但是得loading,并没有真正的解决问题,继续找问题。
3. 运行时控制台报很多warning,是否有影响?
刚开始并没考虑它,感觉一般warning只是警告,不会影响程序运行,但是后来发现每次切换都是这些warning都显示完,页面数据才会加载出来,那断定肯定程序中哪里写的不规范导致warning太多,影响了程序运行速度。
warning分析及解决
1. typeScript类型不匹配导致的warning
因为这里的父子组件传值比较多,嵌套较深,可能开发时候vscode没有检查出问题,但是在运行的时候就提示warning了,一种是类型不匹配,一种是没有赋值,本来需要字符串类型的,结果子组件拿到的是null。
- 解决方案就是把所有父子组件的传值类型都统一了,并且有些字段即使没有值也得传默认的,比如字符串的就传
''
。一顿操作下来,warning减少了很多,但还是没有解决到根源,页面仍然有点卡顿。
2.另外一个报了非常多次(100+条)的warning!!!
最后发现,即使warning减少了一些,但是有一个警告出现了特别多次,导致页面爆栈,就是下面这个:
[Vue warn]: Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
2.1 页面爆栈的原理及影响
- 当大量的 warning 不断产生时,浏览器的 JavaScript 引擎需要处理这些信息,在处理过程中,通常会涉及到函数调用栈的操作,每产生一个新的 warning,可能会触发相关的错误处理函数或日志记录函数等的调用,这些函数调用会在栈上不断堆积。
- 如果 warning 的产生速度过快且数量过多,函数调用栈的空间会逐渐被填满,最终导致栈溢出(爆栈)的情况发生。就好比往一个有限容量的容器里不断地放东西,当放满了之后,再往里放就会溢出来。
- 页面响应速度变慢:在爆栈发生之前,大量的 warning 处理过程已经会消耗浏览器的资源,使得页面的加载速度明显变慢,因为浏览器需要在处理页面正常加载任务的同时,还要花费精力去处理这些 warning 信息。
- 页面无响应或崩溃:一旦爆栈情况发生,页面很可能会变得无响应,因为 JavaScript 引擎已经陷入了栈溢出的困境,无法继续正常执行页面上的其他代码和操作。严重情况下,页面甚至可能直接崩溃,用户将无法正常使用页面。
2.2 排查及解决
经过排查发现,是新加的处理field是否禁用的逻辑出现了问题,disabled的属性值要跟着父组件传过来的值动态变,在watch
中又进行了一次cloneDeep深拷贝,在某些情况下,computed
属性可能会引用其他对象或者函数,当进行深拷贝时,这些引用可能会被错误地复制或者丢失,我修改这块代码,不用深拷贝后warning一下子降到了几个,页面也不慢了,性能得到了很大的提升。
js
const formFields = [
{
label: '输入框1',
prop: 'input1',
type: FormItemType.inputNumber,
disabled: computed(() => props.readonly)
},
{
label: '输入框2',
prop: 'input2',
type: FormItemType.inputNumber,
disabled: computed(() => props.readonly)
},
];
const data = reactive({
formData: formFields
});
watch(() => props.type, (val) => {
const newFields = cloneDeep(formFields);
...
});
总结
- typeScript :
- 既然用了ts,就要遵循它的规则,传值时候类型一定要统一,避免带来一些不必要的问题。
- cloneDeep :
- 深拷贝要慎重使用,它会递归地遍历对象的所有属性,如果对象结构很复杂,有多层嵌套的对象、数组等,这个过程会消耗大量的时间和内存。
- 在一些情况下,深拷贝可能会破坏对象之间原有的引用关系。
- computed :
- 使用
computed
就是想利用它缓存能节省开销的特性,但是有些情况下使用不当反而会带来性能问题,比如复杂的逻辑计算,依赖的参数变化较多,不必要的计算等。
- 使用
以上就是解决该问题的过程及得到的反思,如有不对之处,还请指正。