低代码的前端性能优化

5,853 阅读7分钟

背景

最近领导安排了一个任务,优化目前web端的流程引擎,目前是配置了300多个组件,页面展示慢,并且输入框的在进行输入的时候,有明显的卡顿,页面在用着用着就会浏览器崩溃

基本的流程图

graph TD
调用接口拿到配置 --> 前端组装数据option --> 调用渲染器 -->for循环展示组件-->页面展示

问题点主要有以下三点

  1. 展示慢,初次页面加载缓慢
  2. elementui的input组件会输入卡顿
  3. 内存占用过多,经常在使用的过程中浏览器崩溃,下面的截图是现场反馈的

image.png

先看结果

  1. 优化前后的gif对比

优化前的gif 首屏预计在13秒

kfujj-20rzl.gif

优化后的gif 首屏预计在1秒

fcvvh-nprm9.gif

  1. 输入卡顿和内存占用的问题也排查出来了,这个会在下方的文章中进行说明

排查问题思路

检查接口的响应时间

接口响应100ms左右,不用做什么优化

页面的展示逻辑

这个页面的展示逻辑是从后台拿一个接口拿到配置后,前端组装数据(我们暂且称它为option),传给渲染器组件,渲染器去把这个页面渲染出来

优化方向

拿到这个页面大致要从下方几个点来解决,大概有以下7点(当然下面这些点不是一次就可以发现的,需要用chrome的Devtool和vueDevTool经过分析后才知道,可以看这个
用vueDevtools和Performance进行性能排查分享

  1. 前端的option是否过大,是否可以精简
  2. option是配置项,300多个组件,而且还存储了组件的联动关系,这个对象很大,我们是否可以绕过vue的data的get和set检测,使用Object.freeze(option)。如果不太懂这个的话可以看下Object.freeze的MDN文档
  3. 组件渲染次数是否过多,进行了无用的渲染
  4. 当联动事件的时候检测事件触发是否合理,是否有及时的移除事件的监听
  5. 输入框的卡顿这个用vue.lazy延迟更新是否可以解决
  6. 内存占用过多,是对象相互引用造成无法GC,还是深拷贝过多,还是事件监听过多没有进行remove掉,这个使用chorme的memory或者performance进行分析后再做处理
  7. table是否可以延迟加载

排查问题

option配置是否过多

  1. 打印前端组装好的数据,放在vscode后,发现有44万行。不得不夸赞vscode,44万行一点也不卡顿。 image.png 尤雨溪看到后默默退出了群聊

image.png

查阅后端的接口返回的数据,300多个组件,返回了1万多行,前端经过不同的组装后变成了44万行,很有问题。

经过和对应开发沟通,发现是组件和组件之间会有联动,

比如有一个签字组件,下面有一个签字时间,当签署完后,签字时间会自动显示出来。

目前的做法是

每个组件下面都挂载有所有的组件。

造成的问题就是每添加一个组件,option就是指数级别的增长,如果组件不多,问题暴露不出来,组件越多,问题暴露的越明显

解决方案

用 provide和inject,通过全局注入 v2.cn.vuejs.org/v2/api/#pro…

react 的话可以用context 最终精简下来到了4万多行

option 冻结

目前option 虽然精简到了4万多行,但是还是很大。

在官方文档中我们可以看到下方的截图,vue在data中定义的数据会被拦截,监听变量的get和set ,也是一笔不小的开销。

image.png

我们这个option组装完毕后,就不会在进行更改了,可以将数据冻结,提升页面的渲染效率 不太懂的话可以看下看下Object.freeze的文档

组件渲染次数过多

通过打印目前的页面信息,发现初始化进来的时候,页面内的一些组件会渲染多次。

其实是有一个功能,同一个表单在不同的审批节点,显示的内容不同。 比如 一个表单在下属看到的时候有5个字段,领导看到的有10个字端

那么在目前下属的页面的话,目前的做法是先展示,再隐藏,这样就造成了浪费

这个是怎么发现的呢,我在mounded中打印当前的组件ID,发现会有重复的。

解决办法

先用js把要渲染的数据组装好后再进行页面渲染,后续的显示和隐藏通过事件监听来控制

思考

这个问题我也仔细的思考过,这种问题不应该出现,后来我想了想嗯,也许在某个场景下开发偷懒?

比如这个组件是否隐藏功能是后添加的,可能当时这个开发想着就是堆功能,先展示,再对当前的数据进行隐藏,应该是开发量最小的,包括后续对我们公司的app进行优化的时候也发现了这个问题。

或许我们在进行复杂前端的功能开发前先进行下前端设计文档的输出,然后另外一个人提前介入,看看有没有更好的思路或者设计可以提前规避这些问题

输入卡顿

造成卡顿的原因是当vue的data函数中绑定的对象上数据过多,当不做任何处理,每次输入值都会造成页面的数据刷新,从而造成卡顿。 查看官方文档后发现,可以使用vue.lazy

image.png 用的是elementUI, 发现el-input 组件是不支持直接使用lazy修饰符的

但是从上图我们可以看到,主要的思路是将input事件改为了change事件。

我们可以在el-input内部封装一个变量,当onChange的时候通知外部更改或者更改v-model,这样也可以解决输入卡顿的问题

关于el-input为什么不支持.lazy修饰符的可以看这个issure github.com/vuejs/vue/i…

内存占用过多

第一步我们的option数据的减少和冻结都对内存崩溃有帮助,我们用chrome的开发者工具再分析下,分析的步骤可以看最下方的参考链接,里面的内容讲解的非常详细

发现eventBus占用了很多

4991687685788_.pic.jpg

检查了代码后发现,确实是当页面关闭的时候,一些事件没有移除,这样就造成了页面越用越卡顿,直到浏览器崩溃, 找到原因后,解决办法很简单,每次在on之前先off掉

将table的加载放在第二次循环

经过测试 vue的for 循环的代码 必须等组件内的所有生命周期加载结束才可以展示组件,不是渲染一个组件显示一个

通常情况下,table 的渲染过程较为耗时,尤其是对于具有默认值的可编辑表格。我们可以对渲染器内的代码进行一些调整,将 tableData 赋值的地方用 setTimeout 包裹。将耗时的任务放在事件的下一个循环中,优先展示主要页面。

然而,这种方法也带来了一个问题。例如,如果 table 中的一项是必填的,我们可以在提交按钮上加一个 loading,当加载完成后,通知外部结束 loading。

总结

  1. 当数据量过大,首先要精简数据,或者将数据冻结,提升渲染速度
  2. 当浏览器经常崩溃的时候,一般就是我们的内存过大,通常是变量内容过多,互相引用,event没有及时清除
  3. 进行复杂功能的开发的时候,先设计,再评审,避免一些低级错误的出现,减少返工

参考链接

  1. 手把手教你排查Javascript内存泄漏