js 超详细 保证你看懂学会的发布、订阅者模式

837 阅读5分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

发布订阅模式就是 监听on 派发emit该函数的回调

  • 使用过vueBus 的就了解 on绑定 和使用 emit触发该vueBus;

大致代码就是:

const vueBus = new Vue();

//使用$on  绑定jue-jin
vueBus.$on('jue-jin',() => {
 console.log('我是掘金')
});

//使用$emit 触发jue-jin 绑定的回调
vueBus.$emit('jue-jin')

//解除绑定就是使用 $off
vueBus.$off('jue-jin')


//使用once 绑定 触发一次就解除绑定
vueBus.$once('jue-jin-once',() => {
 console.log('我是掘金once')
});
  • 大致可以分为 4个方法 分别是: on、emit、once、off

那接下来咱们就实现自己的发布、订阅模式吧;

开始

  • 先定义一个咱们的函数 EventEmitter
  • events 存放咱们的订阅的方法
//

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

on

  • on 函数确定参数(2个)
  • 参数1 String
  • 参数2 function 回调函数
  • 把绑定的值放在 events 对象里面 {[kay: String(参数1)]: [function(参数2)] }
  • 值定义成数组的初衷是能对一个key再不同的地方绑定多次
  • 功能: 绑定事件

参数确认了 咱们试试写写看

class EventEmitter {
   constructor() {
     this.events = {}
   }
   /*
   *@dec 接收两个参数
   *@params {String} eventName
   *@params {Function} cb
   */
   on(eventName, cb) {
     //把绑定的放在events里面就是
     if(this.events[eventName]) {
       //绑定过的就放里面push就行了
       this.events[eventName].push(cb)
     } else {
     //没有绑定过的直接定义为数组类型,并把该function 放在数组里面
      this.events[eventName] = [cb]
     }
   }
 }

绑定写好了那咱们接着写emit触发 之前绑定的on 的回调

emit

  • 确定参数2个
  • 参数1 String 也就是对应绑定的on的key
  • 参数2 传参 也就是咱们emit的时候带的参数
  • 功能:触发对应key回调cb

咱们写写这个函数试试

class EventEmitter {
   constructor() {
     this.events = {}
   }
   /*
   *@dec 接收两个参数
   *@params {String} eventName
   *@params {Function} cb
   */
   on(eventName, cb) {
     //把绑定的放在events里面就是
     if(this.events[eventName]) {
       //绑定过的就放里面push就行了
       this.events[eventName].push(cb)
     } else {
     //没有绑定过的直接定义为数组类型,并把该function 放在数组里面
      this.events[eventName] = [cb]
     }
   }
   
   emit(eventName, ...args) {
    //this.events[eventName] 这个时候能看到就是咱们on 绑定的[] { key: []}
    //forEach 循环调用
    //参数args
     this.events[eventName] && this.events[eventName].forEach(cb => {
      cb(...args);
     });
   }
   
 }

写到这就能写小demo验证咱们写的有没有问题了

尝试验证下

//new 一个咱们刚写的函数类
const myEvent = new EventEmitter();

//绑定
myEvent.on('jue-jin-event',(params) => {
 console.log(params, '掘金社区')
});

//触发

myEvent.emit('jue-jin-event', '我是掘金社区的一员,这是三原自定义的发布订、阅者模式')

//输出:我是掘金社区的一员,这是三原自定义的发布订、阅者模式 掘金社区

验证通过

  • 验证通过 输出 我是掘金社区的一员,这是三原自定义的发布订、阅者模式 掘金社区
  • 接下来咱们再写off 解除绑定事件

off

  • 确定参数1个
  • 参数1 就是咱们要解除绑定的key
  • 功能: 解除绑定、释放绑定
class EventEmitter {
   constructor() {
     this.events = {}
   }
   /*
   *@dec 接收两个参数
   *@params {String} eventName
   *@params {Function} cb
   */
   on(eventName, cb) {
     //把绑定的放在events里面就是
     if(this.events[eventName]) {
       //绑定过的就放里面push就行了
       this.events[eventName].push(cb)
     } else {
     //没有绑定过的直接定义为数组类型,并把该function 放在数组里面
      this.events[eventName] = [cb]
     }
   }
   
   emit(eventName, ...args) {
    //this.events[eventName] 这个时候能看到就是咱们on 绑定的[] { key: []}
    //forEach 循环调用
    //参数args
     this.events[eventName] && this.events[eventName].forEach(cb => {
      cb(...args);
     });
   }
   
   off(eventName) {
    if(this.events[eventName]) {
     delete this.events[eventName]
    }
   }
 }

off验证

myEvent.on('off-test',(params) => {
 console.log(params, '掘金社区')
});

myEvent.emit('off-test','我是未解除绑定的off-test');

myEvent.off('off-test');

myEvent.emit('off-test','我是解除绑定的off');

//输出:我是未解除绑定的off-test

off 验证通过

  • off验证通过

once

  • 确定参数2个 跟on函数一样
  • 功能: 只能调用一次就解除绑定;

咱们试着写写看如下

class EventEmitter {
   constructor() {
     this.events = {}
   }
   /*
   *@dec 接收两个参数
   *@params {String} eventName
   *@params {Function} cb
   */
   on(eventName, cb) {
     //把绑定的放在events里面就是
     if(this.events[eventName]) {
       //绑定过的就放里面push就行了
       this.events[eventName].push(cb)
     } else {
     //没有绑定过的直接定义为数组类型,并把该function 放在数组里面
      this.events[eventName] = [cb]
     }
   }
   
   emit(eventName, ...args) {
    //this.events[eventName] 这个时候能看到就是咱们on 绑定的[] { key: []}
    //forEach 循环调用
    //参数args
     this.events[eventName] && this.events[eventName].forEach(cb => {
      cb(...args);
     });
   }
   
   off(eventName) {
    if(this.events[eventName]) {
     delete this.events[eventName]
    }
   }
   
   once(eventName, cb) {
     //只能触发一次就立即解除绑定那
     //自己定义一个函数
     //函数包括: 调用cb回调、解除绑定
     
     const once = (...args) => {
      cb(...args)
      this.off(eventName)
     }
     //绑定
     this.on(eventName, once)
   }
 }

once验证

myEvent.once('once-test',(params) => {
 console.log(params, '掘金社区')
});

myEvent.emit('once-test','我是第一次调用的once-test');

myEvent.emit('once-test','我是第二次调用的once-test');
//输出: 我是第一次调用的once-test 掘金社区

once验证通过

  • 验证通过

实用场景

使用场景就多了

  • 可以在你写插件的时候,帮你和用插件的人实现交互
  • 你只需要继承一下该函数就成了(用法)
class CustomClass  extends events {
  constructor() {
   super()
   
  }
  
  init() {
    this.emit('extends-events', '我是使用发布订阅者的插件哦~~~~~~')
  }
}

const customClass = new CustomClass();
//绑定
customClass.on('extends-events',(params) => {
   console.log(params, '掘金社区')
})

customClass.init();
// 输出:我是使用发布订阅者的插件哦~~~~~~ 掘金社区

结束语

  • 大家好,我是三原
  • 多谢您的观看,希望对您有所帮助
  • 有问题可以指出来,咱们一块沟通

源码:稍后贴出

TS 版本的有需要可以直接copy

type callBack = (arg?: any) => void 


class EventEmitter {
    _events: { [key: string]: any };
    
    constructor() {
        this._events = {}
    }

    public on(eventName: string, cb: callBack): void {
        if (this._events[eventName]) {
            this._events[eventName].push(cb);
        } else {
            this._events[eventName] = [cb];
        }
    }

    public emit(eventName: string, ...args: any): void {
       this._events[eventName] && this._events[eventName].forEach((fn: callBack) => {
            fn(...args);
        });
    }

    public off(eventName: string, cb: callBack): void {
        if (this._events && this._events[eventName]) {
            delete this._events[eventName]
        }
    }

    public once(eventName: string, cb: callBack): void {
        const one = () => {
            cb();
            this.off(eventName, one);
        };

        this.on(eventName, one);
    }

}

export default EventEmitter;