直切事件监听器大动脉,释放终端 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一次,各组件也完整的接受到了信息

写在最后

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

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

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

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

个人能力有限

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

相关推荐
罗_三金几秒前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity8 分钟前
字节二面
前端·面试
东方翱翔15 分钟前
CSS的三种基本选择器
前端·css
Fan_web37 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby