直切事件监听器大动脉,释放终端 20 倍性能

前言

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

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

项目背景

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

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

其中的终端叫做工控机,理解一下 就是工业操控机器,说白了就是供工人操作业务的一个终端

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

技术栈

  • vue2

问题定位

在笔者接手项目的时候,发现其中一个页面过一段时间就奔溃,导致现场屡次投诉,工人们也随之奔溃

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

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

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

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

在线 Demo

老规矩先上 demo

stackblitz.com/edit/vue-74...

现场场景复现

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

代码结构

因为线上的组件结构非常复杂,子组件数量达到了 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去处理对应的业务逻辑

js 复制代码
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 推送消息行为

js 复制代码
<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 举例,其他不再赘述

js 复制代码
<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.vuemixins的监听器移除不就好了吗?

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

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

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

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

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

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

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

此时的

  • mixins.js
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的子组件

同样我们需要借助一个辅助函数,直接安排

js 复制代码
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方法

js 复制代码
created() {
    if (isSelfComponentName(this, 'Parent')) {
      observerList.push(this.onWsMessage) // 统一由 observerList 管理
      window.addEventListener('onmessage', this.onmessage)
    } else if(isChildOf(this,'Parent') {
      observerList.push(this.onWsMessage)
    }
}

收到消息后进行分发

js 复制代码
methods: {
    async onmessage(e) {
      // 开始处理业务逻辑,这里用 fetch 接口代替
      fetch(`https://api.example.com/${Date.now()}`)
      
      // 省略业务逻辑....
      
      // 这里我们就要遍历 observerList
      observerList.forEach(observer=>observer(e.detail))
    }
}

看下优化后的效果 接口 1s一次,各组件也完整的接受到了信息

写在最后

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

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

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

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

个人能力有限

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

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax