注册
web

改造mixins,我释放了20倍终端性能

前言


彦祖们,今天分享一个笔者遇到的真实项目场景, 做了一个项目肿瘤切除术,直接把性能提升 20 倍


认真看完,帮你简历上亮点。阅读本文前,默认彦祖们已经了解 vue.mixins


眼见为实,彦祖们先看下优化前后的性能对比



  • 优化前
    WechatIMG142.jpg
  • 优化后
    WechatIMG143.jpg

项目背景


开始之前,让我们来简述一下项目背景


笔者的项目业务是工业互联网,简而言之就是帮助工厂实现数字化


其中的终端叫做工控机(性能较我们 PC 会相差几十倍),理解一下 就是工业操控机器,说白了就是供工人操作业务的一个终端


类似于我们去医院自助挂号/打印报告的那种终端


技术栈



  • vue2

问题定位


在笔者接手项目(历时三年的老项目,实在是非常痛苦)的时候,发现其中一个页面过一段时间就奔溃无响应,导致现场屡次投诉


这种依附于终端的界面属实不好调试


经过各种手段摸排,我们定位到了问题所在


其实就是 vue mixins 内容部添加了重复的 websocket 事件监听器


导致页面重复渲染,接口重复调用


在线 Demo


老规矩先上 demo


stackblitz.com/edit/vue-74…


现场场景复现


下面笔者简单模拟一下线上的真实代码场景


代码结构


因为线上的组件结构非常复杂,子组件数量达到了 20 个甚至 30 个以上


笔者就抽象了主要问题,模拟了一下 5 个子组件的情况


image.png


总结一下图中的两个关键信息


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 倍...这是非常可怕的事


2023-11-26 11.13.35.gif


开始动刀


接下来让我们一步步来切除这个监听器肿瘤,让终端变得更轻松


定位重复的监听器


现象已经比较明显了


彦祖们大致能猜想到是因为绑定了过多的 onmessage 监听器导致过多的重复逻辑.


我们可以借助 getEventListeners API 来看下指定对象的绑定事件



这个 API 只能在浏览器中调试,无法在代码中使用



chrome devTools 执行一下 getEventListeners(window)


很明显有 6 个重复的监听器(1个 Parent.vue + 5个 Child.vue)


image.png


getEventListeners 介绍


彦祖们这个 API 对于事件监听类的代码优化还是蛮有效的


我们还可以右键 listener 定位到具体的赋值函数
2023-11-26 11.25.18.gif


切除重复的监听器


目标已经很明确了,我们只需要一个 onmessage 监听器就足够了


那么把 child.vuemixins的监听器移除不就好了吗?


彦祖们可能会想到最简单的方案,就是把 mixins 改成函数形式,通过传参判断是否需要添加监听器


但是因为实际业务的复杂性,上文中也提到了 mixins 同时也被其他多个文件所引用,最终这个方案被 pass 了


那么我们可以反向思考一下,只给 Parent.vue 添加监听器


需要一个辅助函数来判断是否为 Parent.vue,直接看代码吧


const isSelfByComponentName = (vm, componentName) => {
// 这里借助了 element 的思路,新增了 componentName 属性,不影响 name 属性
return vm.$options.componentName === componentName
}

让我们来测试一下,很完美,为什么第一个 true 就能确定是父组件呢?


如果不了解的彦祖,建议你看下父子组件的加载渲染顺序


image.png


此时的



  • 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一次, 但是新问题随即出现了


2023-11-26 12.18.50.gif


我们会发现此时只有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
}

测试一下,不用看 就是自信


image.png


核心代码


彦祖们核心代码来了!


我们在 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一次,各组件也完整的接受到了信息


2023-11-26 12.16.25.gif


当然,除此之外,笔者还做了很多的性能优化手段
比如


1.把大量的 O(n^2) 的算法降维到了 O(n)


2.把非实时性数据做了节流保护


3.大量的template表达式语法迁移到了 computed


4.针对重复的赋值更新逻辑进行了拦截


5.利用 requestIdleCallback 在空闲帧执行 echarts 的渲染


写在最后


之前有彦祖问过笔者,什么才算是面试简历中的亮点


如果笔者是面试官,我觉得 能用最细碎的知识点 解决最复杂的业务问题 绝对算的上是项目亮点


文中的各个知识点,彦祖们应该都非常熟悉


能把你的八股文知识,转换成真正解决业务问题的能力,这是非常难得的


个人能力有限


如有不对,欢迎指正🌟 如有帮助,建议小心心大拇指三连🌟


作者:前端手术刀
来源:juejin.cn/post/7304973928039284777

0 个评论

要回复文章请先登录注册