改造mixins,我释放了20倍终端性能
前言
彦祖们,今天分享一个笔者遇到的真实项目场景, 做了一个项目肿瘤切除术
,直接把性能提升 20 倍
认真看完,帮你简历上亮点。阅读本文前,默认彦祖们已经了解 vue.mixins
眼见为实,彦祖们先看下优化前后的性能对比
- 优化前
- 优化后
项目背景
开始之前,让我们来简述一下项目背景
笔者的项目业务是工业互联网
,简而言之就是帮助工厂实现数字化
其中的终端叫做工控机
(性能较我们 PC 会相差几十倍),理解一下 就是工业操控机器,说白了就是供工人操作业务的一个终端
类似于我们去医院自助挂号/打印报告的那种终端
技术栈
- vue2
问题定位
在笔者接手项目(历时三年的老项目,实在是非常痛苦)的时候,发现其中一个页面过一段时间就奔溃无响应,导致现场屡次投诉
这种依附于终端的界面属实不好调试
经过各种手段摸排,我们定位到了问题所在
其实就是 vue mixins
内容部添加了重复的 websocket
事件监听器
导致页面重复渲染,接口重复调用
在线 Demo
老规矩先上 demo
现场场景复现
下面笔者简单模拟一下线上的真实代码场景
代码结构
因为线上的组件结构非常复杂,子组件数量达到了 20 个甚至 30 个以上
笔者就抽象了主要问题,模拟了一下 5 个子组件的情况
总结一下图中的两个关键信息
1.child 子组件可能 会被多个父组件
引用
2.child 子组件的层级是不固定
代码目录结构大致如下
- Parent.vue // 主页面
- mixins
- index.js // 核心的 mixin 文件
- component
- child1.vue // 子组件
- grandchild1.vue // 孙子组件
- child2.vue
- child3.vue
- child4.vue
- child5.vue
代码说明
接下来让我们简单来看下项目中各个代码文件的主要作用
- mixins.js
剥离业务逻辑后,核心就是增加了一个onmessage
事件监听器
最后通过各自子组件自定义的onWsMessage
去处理对应的业务逻辑
export const wsMixin = {
created() {
window.addEventListener('onmessage', this.onmessage)
},
beforeDestory() {
window.addEventListener('onmessage', this.onmessage)
},
methods: {
// ... 省略其他业务方法
async onmessage(e) {
// 开始处理业务逻辑,这里用 fetch 接口代替,当然实际业务比这复杂太多
fetch(`https://api.example.com/${Date.now()}`)
// ...
// 开始处理对应的业务逻辑
this.onWsMessage(e.detail)
}
}
}
- Parent.vue
引入子组件,并且模拟了 websocket
推送消息行为
<template>
<div id="app">
<Child1 />
<Child2 />
<Child3 />
<Child4 />
<Child5 />
</div>
</template>
<script>
import Child1 from './components/Child1.vue'
import Child2 from './components/Child2.vue'
import Child3 from './components/Child3.vue'
import Child4 from './components/Child4.vue'
import Child5 from './components/Child5.vue'
import { wsMixin } from './mixins'
// 模拟 websocket 1s 推送一次消息
setInterval(() => {
const event = new CustomEvent('onmessage', {
detail: { currentTime: new Date() }
})
window.dispatchEvent(event)
}, 1000)
export default {
name: 'Parent',
components: { Child1, Child2, Child3, Child4, Child5 },
mixins: [wsMixin],
methods: {
onWsMessage(data) {
console.log('parent onWsMessage', data)
}
}
}
</script>
- child.vue
child.vue
核心逻辑都非常相似,此处以 child1.vue
举例,其他不再赘述
<template>
<div>
child1
</div>
</template>
<script>
import { wsMixin } from '../mixins'
export default {
mixins: [wsMixin],
methods: {
onWsMessage(data) {
console.log('child1 onWsMessage', data)
// 处理业务逻辑
}
}
}
</script>
现场预览
彦祖们,让我们来看一下模拟的现场
我们期望的效果应该是 onmessage
收到消息后,会发送一次请求
但是目前来看显然是发送了 6 次请求
实际线上更为复杂可能高达 20 倍,30 倍...这是非常可怕的事
开始动刀
接下来让我们一步步来切除这个监听器肿瘤
,让终端变得更轻松
定位重复的监听器
现象已经比较明显了
彦祖们大致能猜想到是因为绑定了过多的 onmessage
监听器导致过多的重复逻辑.
我们可以借助 getEventListeners
API 来看下指定对象的绑定事件
这个 API 只能在浏览器中调试,无法在代码中使用
在 chrome devTools
执行一下 getEventListeners(window)
很明显有 6 个重复的监听器(1个 Parent.vue + 5个 Child.vue)
getEventListeners 介绍
彦祖们这个 API 对于事件监听类的代码优化还是蛮有效的
我们还可以右键 listener 定位到具体的赋值函数
切除重复的监听器
目标已经很明确了,我们只需要一个 onmessage
监听器就足够了
那么把 child.vue
中 mixins
的监听器移除不就好了吗?
彦祖们可能会想到最简单的方案,就是把 mixins
改成函数形式,通过传参判断是否需要添加监听器
但是因为实际业务的复杂性,上文中也提到了 mixins
同时也被其他多个文件所引用,最终这个方案被 pass 了
那么我们可以反向思考一下,只给 Parent.vue
添加监听器
需要一个辅助函数来判断是否为 Parent.vue
,直接看代码吧
const isSelfByComponentName = (vm, componentName) => {
// 这里借助了 element 的思路,新增了 componentName 属性,不影响 name 属性
return vm.$options.componentName === componentName
}
让我们来测试一下,很完美,为什么第一个 true 就能确定是父组件呢?
如果不了解的彦祖,建议你看下父子组件的加载渲染顺序
此时的
- mixins.js
const isSelfComponentName = (vm, componentName) => {
return vm.$options.componentName === componentName
}
export const wsMixin = {
created() {
console.log('__SY__🍦 ~ created ~ isSelfComponentName', isSelfComponentName(this, 'Parent'))
if (isSelfComponentName(this, 'Parent')) window.addEventListener('onmessage', this.onmessage)
},
beforeDestory() {
window.removeEventListener('onmessage', this.onmessage)
},
methods: {
async onmessage(e) {
// 开始处理业务逻辑,这里用 fetch 接口代替
fetch(`https://api.example.com/${Date.now()}`)
console.log('__SY__🍦 ~ onmessage ~ e:', e)
// 省略处理统一逻辑....
// 开始处理对应的业务逻辑
this.onWsMessage(e.detail)
}
}
}
如何进行子组件的消息分发?
前面我们已经把多余的监听器给切除了,网络请求的确变成了 1s一次
, 但是新问题随即出现了
我们会发现此时只有Parent.vue
触发了onWsMessage
child.vue
的对应的 onWsMessage
并没有触发
那么此时的核心问题就是 如何从父组件的监听事件中分发消息给多个子组件?
利用观察者模式思想解决消息分发
我们可以借助观察者模式思想来实现这个功能
解决这个问题还有个前提,我们得知道哪些组件是 Parent.vue
的子组件
同样我们需要借助一个辅助函数,直接安排
const isChildOf = (vm, componentName) => {
let parent = vm.$parent
// 这里为什么要向上遍历呢?因为前面提到了,子组件的层级是不固定的
while (parent) {
if (parent.$options.componentName === componentName) return true
parent = parent.$parent
}
return false
}
测试一下,不用看 就是自信
核心代码
彦祖们核心代码来了!
我们在 mixins.js
初始化一个 observerList=[]
, 用来存储子组件的 onWsMessage
方法
created() {
if (isSelfComponentName(this, 'Parent')) {
observerList.push(this.onWsMessage) // 统一由 observerList 管理
window.addEventListener('onmessage', this.onmessage)
} else if(isChildOf(this,'Parent') {
observerList.push(this.onWsMessage)
}
}
收到消息后进行分发
methods: {
async onmessage(e) {
// 开始处理业务逻辑,这里用 fetch 接口代替
fetch(`https://api.example.com/${Date.now()}`)
// 省略业务逻辑....
// 这里我们就要遍历 observerList
observerList.forEach(observer=>observer(e.detail))
}
}
看下优化后的效果
接口 1s一次
,各组件也完整的接受到了信息
当然,除此之外,笔者还做了很多的性能优化手段
比如
1.把大量的 O(n^2)
的算法降维到了 O(n)
2.把非实时性数据做了节流保护
3.大量的template表达式语法
迁移到了 computed
中
4.针对重复的赋值更新逻辑
进行了拦截
5.利用 requestIdleCallback
在空闲帧执行 echarts
的渲染
写在最后
之前有彦祖问过笔者,什么才算是面试简历中的亮点
如果笔者是面试官,我觉得 能用最细碎的知识点 解决最复杂的业务问题
绝对算的上是项目亮点
文中的各个知识点,彦祖们应该都非常熟悉
能把你的八股文知识
,转换成真正解决业务问题的能力,这是非常难得的
个人能力有限
如有不对,欢迎指正🌟 如有帮助,建议小心心大拇指三连🌟
来源:juejin.cn/post/7304973928039284777