美文网首页JavaScript 进阶营
自己实现一个eventBus

自己实现一个eventBus

作者: a1838b5b5d28 | 来源:发表于2018-09-20 09:53 被阅读72次

什么是发布/订阅模式?

一张图来解释:


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)
  }
}

相关文章

  • 自己实现一个eventBus

    什么是发布/订阅模式? 一张图来解释: 发布者发布一项内容给控制中心 控制中心将内容发送给对应的订阅者 当我们需...

  • 自己实现一个eventBus

    昨天使用了vue提供的事件发布订阅,于是今天就自己实现了一个,发现没有太大难度,直接上代码: on、off和emi...

  • EventBus源码解析

    知识点汇总: 一:EventBus框架概述 二:EventBus的注册实现原理 三:EventBus的事件分发实现...

  • 如何自己实现一个 EventBus

    什么是 EventBus EventBus 是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码去实...

  • vue兄弟组件之间通信--eventBus

    原文地址 eventBus eventBus单独的事件中心,用来管理组件之间的通信。 由于 Vue 实例实现了一个...

  • 动手造轮子:实现一个简单的 EventBus

    动手造轮子:实现一个简单的 EventBus Intro EventBus 是一种事件发布订阅模式,通过 Even...

  • [Flutter]EventBus的使用和底层实现分析

    什么是EventBus EventBus是全局事件总线,底层通过Stream来实现;它可以实现不同页面的跨层访问,...

  • EventBus的使用

    什么是EventBus EventBus是全局事件总线,底层通过Stream来实现;它可以实现不同页面的跨层访问,...

  • 一个eventBus

    vue实现一个eventBus eventBus应该有些听过,其实就是一个事件发布订阅的功能。vue提供了实例方法...

  • EventBus

    一、EventBus的原理 EventBus是全局事件总线,底层通过Stream来实现;它可以实现不同页面的跨层访...

网友评论

    本文标题:自己实现一个eventBus

    本文链接:https://www.haomeiwen.com/subject/yxsgnftx.html