面试被问到JavaScript设计模式“发布-订阅”你有了解吗

2,383 阅读5分钟

前言

大家好,今天简单实现一个发布-订阅模式,相信大家对这个模式都不陌生,或多或少也都见过或者听过,日常开发中,用到的地方也不少,举一个常见的例子Vue中的响应式原理中使用了发布-订阅模式

并且呢,在当前环境下的面试中也会常被问到一道题

你经常使用的设计模式有哪些,有什么作用,如何实现它?

那么今天就来聊聊发布-订阅者模式

概念

这呢里先说明一下什么是发布-订阅模式

首先呢发布-订阅模式的发布和订阅都由统一的一个调度中心来处理,那也就是说这个模式呢是有三部分组成的

  • 发布者:将消息事件发布到调度中心
  • 订阅者: 把自己想关注的消息事件,注册到调度中心
  • 调度中心:处理事件注册与发布

有什么作用呢,就是在异步编程中实现更松的解耦

1.png

实现

先列举下需要实现发布-订阅模式的思路,目的呢就是实现三个方法,添加删除派发

  1. 需要一个事件变量对象来存储消息事件,因为呢订阅者是不止一个
  2. 定义一个添加方法,将事件添加到事件变量
  3. 定义一个删除方法,将事件于事件变量中删除
  4. 定义一个派发方法,调用事件变量中的事件

那我们来根据上述四条内容先来实现一个基本的架子

class EventSubscription{
    constructor(){
        // 事件变量对象
        this.eventVarObject = {};
    }
​
    // 事件添加方法
    addEventFun(){}
​
    // 事件删除方法
    deleteEventFun(){}
​
    // 派发方法
    emitEventFun(){}
}

继续来详细分析每一个方法需要做什么事情

addEventFun

addEventFun事件添加方法

  • 添加消息事件是需要一个事件名的,以及消息事件的事件回调函数
  • 需要将消息事件添加到事件变量中,并且不止一个变量
  • 添加事件变量时需要判断是否存在当前事件,做不同的处理

那我们来继续优化addEventFun方法

/**
 * 事件添加方法
 * @param {'*'} name 事件名
 * @param {*} callback 事件回调函数
 */
addEventFun(name, callback) {
  // 判断事件变量中是否有当前事件, 如果没有的话初始化一个空数组
  if (!this.eventVarObject[name]) this.eventVarObject[name] = [];
  // 如果存在,那就是说明该事件多个订阅者,继续往后push
  this.eventVarObject[name].push(callback);
}

deleteEventFun

deleteEventFun事件删除方法

  • 删除消息事件是需要一个事件名的,以及消息事件的事件回调函数
  • 事件变量中,对应的事件回调函数不止一个,需要对符合条件的删除,仅删除这个callback
  • 如果事件回调函数不存在的话,直接删除事件

那我们来继续优化deleteEventFun方法

/**
 * 事件删除方法
 * @param {*} name 事件名
 * @param {*} callback 事件回调函数
 */
function deleteEventFun(name, callback) {
  // 事件回调函数不存在,删除相应整个事件
  if (!callback){
      delete this.eventVarObject[name];
      return
  };
​
  // 需要对符合条件的删除
  const index = this.eventVarObject[name].findIndex((item) => item == callback);
  this.eventVarObject[name].splice(index, 1);
}

emitEventFun

emitEventFun事件派发方法

  • 事件派发方法只需要事件名,确定是哪个事件,也可以传递参数
  • 事件变量中,对应的事件回调函数依次执行

那我们来继续优化emitEventFun方法

/**
 * 派发方法
 * @param {*} name 事件名
 * @param {*} data 参数
 */
function emitEventFun(name, data) {
  // 确认事件是否订阅
  if (!this.eventVarObject[name]) return;
​
  // 依次执行事件回调函数
  this.eventVarObject[name].forEach((callback) => {
    callback(...data);
  });
}

到目前为止,整个发布-订阅模式的功能已经基本实现,还缺少一些错误逻辑的处理,在这我就不添加了

完整代码

class EventSubscription {
  constructor() {
    // 事件变量对象
    this.eventVarObject = {};
  }
​
  /**
   * 事件添加方法
   * @param {'*'} name 事件名
   * @param {*} callback 事件回调函数
   */
  addEventFun(name, callback) {
    // 判断事件变量中是否有当前事件, 如果没有的话初始化一个空数组
    if (!this.eventVarObject[name]) this.eventVarObject[name] = [];
    // 如果存在,那就是说明该事件多个订阅者,继续往后push
    this.eventVarObject[name].push(callback);
  }
​
  /**
   * 事件删除方法
   * @param {'*'} name 事件名
   * @param {*} callback 事件回调函数
   */
  deleteEventFun(name, callback) {
    // 事件回调函数不存在,删除相应整改事件
    if (!callback){
        delete this.eventVarObject[name];
        return
    };
​
    // 需要对符合条件的删除
    const index = this.eventVarObject[name].findIndex(
      (item) => item == callback
    );
    this.eventVarObject[name].splice(index, 1);
  }
​
  /**
   * 派发方法
   * @param {*} name 事件名
   * @param {*} data 参数
   */
  emitEventFun(name, data) {
    // 确认事件是否订阅
    if (!this.eventVarObject[name]) return;
​
    // 依次执行事件回调函数
    this.eventVarObject[name].forEach((callback) => {
      callback(...data);
    });
  }
}

那最后呢,来个demo来测试下是否正确

const eventSubscription = new EventSubscription();
​
function test1(params) {
  console.log("测试test1:", params);
}
function test2(params) {
  console.log("测试test2:", params);
}
​
// 添加订阅内容
eventSubscription.addEventFun('test', test1);
eventSubscription.addEventFun('test', test2);
console.log(eventSubscription)
// 输出结果
// EventSubscription {
//     eventVarObject: { test: [ [Function: test1], [Function: test2] ] }
// }// 派发订阅事件
eventSubscription.emitEventFun('test', 'tests事件触发')
// 输出结果
// 测试test1: tests事件触发
// 测试test2: tests事件触发// 删除订阅事件
eventSubscription.deleteEventFun('test', test2)
console.log(eventSubscription)
// 输出结果
// EventSubscription { eventVarObject: { test: [ [Function: test1] ] } }
​
eventSubscription.deleteEventFun('test')
console.log(eventSubscription)
// 输出结果
// EventSubscription { eventVarObject: {} }

结语

因为呢,我在参加掘金日新计划活动,所以来请个赞,如果感觉此文稍微有些帮助的话,请不吝点个赞🥺🥺🥺,帮我加个油

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情