美文网首页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

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