JavaScript 进阶营

自己实现一个eventBus

2018-09-20  本文已影响72人  a1838b5b5d28

什么是发布/订阅模式?

一张图来解释:


eventbus.png

发布者发布一项内容给控制中心 控制中心将内容发送给对应的订阅者

当我们需要在完全没有联系的组件之间通信,经常会用到,下面开始code。

开始code一个最简单的

首先我们先创建一个类 EventBus

class EventBus {

}

类创建好了 我们需要思考一下我们这个类需要哪些方法和属性。
首先我们肯定需要一个发布方法 一个订阅方法。其次我们还需要一个存储中心来存储事件以及对应的订阅函数,下面使我们目前想到的方法和属性。

  1. 发布方法 fire
  2. 订阅方法 on
  3. 存储中心 events

ok 我们的eventBus类现在长成了这样

class EventBus {
  constructor() {
    this.events = {}
  }

  on = (eventName, callback) => {
    this.events[eventName] = callback; // 以事件名为key 订阅函数为value存储在events里
  }

  fire = (eventName) => {
    this.events[eventName]() // 调用事件对应的订阅函数
  }
}

现在完成了一个最简单的eventBus, 我们来测试一下 看看效果

 const eventBust = new EventBus()
 eventBus.on('greet'); // 订阅事件 greet

 setTimeout(() => {
   eventBus.fire('greet') // 3s 后触发 greet 事件
 }, 3000);

eventBus_1.gif

给事件的订阅函数传递参数

ok 这个简单的eventBus可以正常运行了,下面我们要是想在触发事件的同时传一些参数进去呢?
我们将fire改造一下就可以啦 用上扩展运算法 ‘...’ 想传几个参数就传几个参数。

fire(eventName, ...arg) {
    this.events[eventName].call(this, ...arg) // 使用... 扩展运算符将所有参数传入订阅函数
  }

测试一下

const eventBus = new EventBus()

eventBus.on('greet', (name1, name2) => {
  console.log(`hello ${name1} and ${name2} !`);
}); // 订阅事件 greet

setTimeout(() => {
  eventBus.fire('greet', '小明', '小红') // 3s 后触发 greet 事件 并传入两个参数
}, 3000);
eventBus_2.gif

取消事件的所有订阅程序

如果我们想要取消对某个事件的订阅呢?
回顾一下 我们现在的eventBus具有的方法和属性,同时添加一个取消的方法。这是现在eventBus的样子

  1. 发布方法 fire
  2. 订阅方法 on
  3. 存储中心 events
  4. 取消订阅某一事件的所有方法 offAll

改造一下fire并添加一个offAll方法

fire(eventName, ...arg) {
    if(typeof this.events[eventName] === 'function') {
      this.events[eventName].call(this, ...arg);
    }
  }
offAll(eventName) {
    Reflect.deleteProperty(this.events, eventName); // 删除对某一事件的订阅
  }

测试一下

const eventBus = new EventBus()
eventBus.on('greet', (name1, name2) => {
  console.log(`hello ${name1} and ${name2} !`);
}); // 订阅事件 greet

eventBus.offAll('greet'); // 取消订阅

setTimeout(() => {
  eventBus.fire('greet', '小明', '小红') // 3s 后触发 greet 事件
  console.log('若上面没有log出内容 说明取消订阅成功');
}, 3000);

eventBus_3.gif

取消事件特定的订阅程序

上面的offAll 是取消了对某一事件订阅所有订阅函数 加入我们只想取消某一个订阅程序呢?
加一个off方法, 同事改造一下on和fire方法

  on(eventName, callback) {
      // 可能同一个事件注册了不同的订阅函数
      if(this.events[eventName]) { 
        this.events[eventName].push(callback);
      }else {
        this.events[eventName] = [callback];
      }
    }

  fire(eventName, ...arg) {
    if(Reflect.has(this.events, eventName)) {
      this.events[eventName].forEach(fn => {
        fn.call(this, ...arg);
      });
    }
  }

  off(eventName, fnName) { // 根据订阅程序的函数名称移除
    const fns = this.events[eventName];
    const targetIndex = fns.findIndex(fn => (fn.name === fnName));
    fns.splice(targetIndex, 1); // 删除指定订阅函数
  }

测试一下

const eventBus = new EventBus()
const fn1 = () => {
  console.log('hello fn1 !!');
}
const fn2 = () => {
  console.log('hello fn2 !!');
}
eventBus.on('greet',fn1); // 订阅事件 greet
eventBus.on('greet',fn2); // 订阅事件 greet
eventBus.off('greet', 'fn1'); // 取消对greet事件的订阅函数 fn1
setTimeout(() => {
  eventBus.fire('greet') // 3s 后触发 greet 事件
}, 3000);

eventBus_4.gif

从上图可看到 只执行了fn2 没有执行fn1 说明取消订阅fn1的函数 成功!

添加类型校验

这个eventBus基本完成了我们还要在优化一下 添加一些类型校验,使代码更加健壮
最终完成的代码如下

/**
 * events: {}
 * events.key @type string   事件名称
 * events.value @type array  事件注册时的回调函数组成的数组
 */

class EventBus {
  constructor() {
    this.events = {}
  }

  /**
   * 注册对事件的订阅
   * @param {*} eventName 事件名称
   * @param {*} callback 订阅程序
   * @memberof EventBus
   */
  on(eventName, callback) {
    if(typeof eventName !== 'string') {  
      throw new Error('eventName expected string');
    }
    if(typeof callback !=='function') {
      throw new Error('callback expected function');
    }
    // 可能同一个事件注册了不同的回调函数
    if(this.events[eventName]) { 
      this.events[eventName].push(callback);
    }else {
      this.events[eventName] = [callback]; //  此事件还未注册回调
    }
  }

  /**
   *触发事件
   *
   * @param {*} eventName 事件名称
   * @param {*} arg 传给订阅程序的参数
   * @memberof EventBus
   */
  fire(eventName, ...arg) {
    if(typeof eventName !== 'string') {  
      throw new Error('eventName expected string');
    }
    if(Reflect.has(this.events, eventName)) {
      this.events[eventName].forEach(fn => {
        fn.call(this, ...arg);
      });
    }
  }

  /**
   * 取消订阅指定函数的事件
   *
   * @param {*} eventName 事件名称
   * @param {*} fnName 订阅程序的名称
   * @memberof EventBus
   */
  off(eventName, fnName) {
    const fns = this.events[eventName];
    const targetIndex = fns.findIndex(fn => (fn.name === fnName));
    fns.splice(targetIndex, 1); // 删除指定回调
  }

  /**
   *取消所有指定事件的订阅
   *
   * @param {*} eventName 事件名称
   * @memberof EventBus
   */
  offAll(eventName) {
    Reflect.deleteProperty(this.events, eventName)
  }
}

上一篇 下一篇

猜你喜欢

热点阅读