cloneDeep 与 computed “联手搞破坏”:揭开不当使用导致性能崩溃的真相

在项目开发进程中,遇到这样一个问题:在 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);
    ...
});

总结

  1. typeScript :
    • 既然用了ts,就要遵循它的规则,传值时候类型一定要统一,避免带来一些不必要的问题。
  2. cloneDeep :
    • 深拷贝要慎重使用,它会递归地遍历对象的所有属性,如果对象结构很复杂,有多层嵌套的对象、数组等,这个过程会消耗大量的时间和内存。
    • 在一些情况下,深拷贝可能会破坏对象之间原有的引用关系。
  3. computed :
    • 使用computed就是想利用它缓存能节省开销的特性,但是有些情况下使用不当反而会带来性能问题,比如复杂的逻辑计算,依赖的参数变化较多,不必要的计算等。

以上就是解决该问题的过程及得到的反思,如有不对之处,还请指正。

相关推荐
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安1 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
编程百晓君1 小时前
一文解释清楚OpenHarmony面向全场景的分布式操作系统
vue.js
暴富的Tdy1 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se1 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235611 小时前
web 渗透学习指南——初学者防入狱篇
前端
z千鑫2 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
m0_748250742 小时前
Web入门常用标签、属性、属性值
前端