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

写在最后

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

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

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

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

个人能力有限

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

相关推荐
ZJ_.8 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营12 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端39 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8543 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235612 小时前
从零开始学前端之HTML(三)
前端·html