设计模式 -发布订阅模式

146 阅读3分钟

前言

发布-订阅模式作为设计模式中的一种,在面试中是一个经常被问到的问题。在本篇文章中,作者将带领大家了解发布订阅模式,和逐步完成发布-订阅模式的函数封装

发布-订阅模式是什么

发布-订阅模式是一种消息传递模式,发布者发送消息时不会直接发送给订阅者,而是发送到一个中转站中,所有订阅了该中转站的订阅者都会统一接受该消息,通过使用这种模式可以使发布者和订阅者不必关系对方的存在,只需要关系中转站。

发布-订阅模式的优点有哪些

  1. 模块化:发布者和订阅者可以独立地增长和变化,而不影响其他部分。
  2. 可扩展性:向系统中添加新的订阅者很简单,只需要订阅他们感兴趣的主题即可。
  3. 异步处理:消息的发布和订阅是异步进行的,可以实现高效的异步处理。

发布-订阅模式的函数封装

在面试中,面试官通常会给我们下面一串代码,让我们完成发布-订阅模式的封装.

class EventEmitter {
  constructor() {

  }
  //订阅事件
  on() {

  }
  //订阅事件,只执行一次
  once() {

  }
  //发布事件
  emit() {

  }
  //取消订阅
  off() {

  }
}

在开始发布订阅模式的封装前,让我们先来看一个小例子明白发布-订阅模式的具体流程

<script>
  //新建一个"promise"事件
  let ev = new Event("promise");
  //订阅"promise"事件,当这个事件执行时,调用fnB
  window.addEventListener("promise", () => {
    fnB();
  });

  function fnA() {
    setTimeout(() => {
      console.log("请求A完成");
      //发布ev事件
      window.dispatchEvent(ev);
    }, 1000);
  }
  
  function fnB() {
    setTimeout(() => {
      console.log("请求B完成");
    }, 500);
  }
  
  fnA();
</script>

在上面的例子中,我们订阅了一个"promise"事件,当"promise"事件发布时执行fnB。而"promise"事件只有在fnA执行完之后才被发布出来。这就是发布-订阅模式的流程。现在让我们来开始发布-订阅的函数封装

首先,我们需要在constructor中定义一个空对象来作为被订阅事件的容器。

  constructor() {
    this._event = {}
  }

on() 订阅事件的实现

  on(type, cb) {
    //没有事件时往this._event中添加一个key为type,value为[cb]的键值对
    if (!this._events[type]) {
      this._events[type] = [cb];
    } else {
    //将cb作为值push进数组中
      this._events[type].push(cb);
    }
  }
  
let ev = new EventEmitter();
const fn1 = (str) => {
  console.log(str, "fn1");
};
const fn2 = (str) => {
  console.log(str, "fn2");
};
const fn3 = (str) => {
  console.log(str, "fn3");
};

ev.on("run", fn1);
ev.on("run", fn2);
ev.on("run", fn3);
//此时this._event为
//{ "run": [fn1,fn2,fn3] }

emit() 发布事件的实现


//为什么要用...args呢?  那是因为当我们调用emit发布事件的时候,可能不止传入一个参数
  emit(type, ...args) {
    if (this._events[type]) {
      //遍历key为type的键值对,逐步执行value数组里面的函数
      this._events[type].forEach((cb) => {
        cb(...args);
      });
    } else {
      console.log("没有这个事件");
      return;
    }
  }

这时我们将emit和上面的on一起调用就能实现一个简单的发布订阅模式 image.png

但是这肯定还是不够的,面试官还会让我们完成once()和off()的实现

off() 取消订阅事件的实现

  off(type, cb) {
    if (this._events[type]) {
        //利用过滤器把要取消的事件过滤掉,并重新赋值给this._events
      this._events[type] = this._events[type].filter((item) => item !== cb);
    } else {
      console.log("没有这个事件");
    }
  }

image.png

once() 订阅事件,只执行一次

  once(type, cb) {
    const fn = (...args) => {
      cb(...args);
      this.off(type, fn);
    };
    //当emit()时 fn被触发一次,随后便通过this.off取消事件,再次emit时this._event[type]中没有fn
    this.on(type, fn);
  }

image.png 到了这里完整的发布-订阅模式便完成了封装,以下是完整代码,如果本篇文章对你有帮助的话就点给个免费的赞激励下作者吧.

class EventEmitter {
  constructor() {
    this._events = {}; //"run" : [fun]
  }
  //订阅事件
  on(type, cb) {
    if (!this._events[type]) {
      this._events[type] = [cb];
    } else {
      this._events[type].push(cb);
    }
  }
  //  
  once(type, cb) {
    const fn = (...args) => {
      cb(...args);
      this.off(type, fn);
    };
    this.on(type, fn);
  }
  //发布事件
  emit(type, ...args) {
    if (this._events[type]) {
      this._events[type].forEach((cb) => {
        cb(...args);
      });
    } else {
      console.log("没有这个事件");
      return;
    }
  }
  //取消订阅
  off(type, cb) {
    if (this._events[type]) {
      this._events[type] = this._events[type].filter((item) => item !== cb);
    } else {
      console.log("没有这个事件");
    }
  }
}