Skip to content

发布订阅者模式

TIP

什么是发布订阅者模式?简单说发布订阅模式就是 监听on 派发emit该函数的回调

vueBus

TIP

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

大致代码就是:

js
const vueBus = new Vue();

//使用$on  绑定blog
vueBus.$on("blog", () => {
  console.log("我是CSC");
});

//使用$emit 触发blog 绑定的回调
vueBus.$emit("blog");

//解除绑定就是使用 $off
vueBus.$off("blog");

//使用once 绑定 触发一次就解除绑定
vueBus.$once("blog-once", () => {
  console.log("我是CSConce");
});

WARNING

大致可以分为 4个方法 分别是: on、emit、once、off,那接下来咱们就实现自己的发布、订阅模式吧。

Start

  • 先定义一个咱们的函数 EventEmitter
  • events 存放咱们的订阅的方法
js
class EventEmitter {
  constructor() {
    this.events = {};
  }
}

on

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

  • 确定参数2个
  • 参数1 String 也就是对应绑定的on的key
  • 参数2 传参 也就是咱们emit的时候带的参数
  • 功能:触发对应key回调cb
js
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);
      });
  }
}

验证on、emit

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

//绑定
myEvent.on("blog-event", (params) => {
  console.log(params, "CSC社区");
});

//触发

myEvent.emit(
  "blog-event",
  "我是CSC社区的一员,这是CSC自定义的发布订、阅者模式",
);

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

on、emit验证通过

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

off

  • 确定参数1个
  • 参数1 就是咱们要解除绑定的key
  • 功能: 解除绑定、释放绑定
js
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验证

js
myEvent.on("off-test", (params) => {
  console.log(params, "CSC社区");
});

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

myEvent.off("off-test");

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

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

off 验证通过

  • off验证通过

once

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

咱们试着写写看如下

js
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验证

js
myEvent.once("once-test", (params) => {
  console.log(params, "CSC社区");
});

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

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

once验证通过

  • 验证通过

实用场景

  • 可以在你写插件的时候,帮你和用插件的人实现交互
  • 你只需要继承一下该函数就成了(用法)
js
class CustomClass extends events {
  constructor() {
    super();
  }

  init() {
    this.emit("extends-events", "我是使用发布订阅者的插件哦~~~~~~");
  }
}

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

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

结束语

TIP

当然社区也有好多比较好的npm包可以直接用,比如:eventemitter2...。 宗旨在会的基础上去使用

  • TS 版本的有需要可以直接copy
ts
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;
  • 基于 eventemitter2封装
ts
import { EventEmitter2, event, ListenerFn } from "eventemitter2";

const MAX_LISTENERS = 9999;

class EventEmitter {
  private eventEmitter: EventEmitter2 = new EventEmitter2();
  public emit = this.eventEmitter.emit.bind(this.eventEmitter);
  public on = this.eventEmitter.on.bind(this.eventEmitter);
  public once = this.eventEmitter.once.bind(this.eventEmitter);

  constructor() {
    this.eventEmitter.setMaxListeners(MAX_LISTENERS);
  }

  public off(eventName: event, listener?: ListenerFn) {
    if (listener !== undefined) {
      return this.eventEmitter.off(eventName, listener);
    } else {
      return this.eventEmitter.removeAllListeners(eventName);
    }
  }
}

export { EventEmitter };
export default new EventEmitter();