JavaScript 设计模式——发布-订阅/观察者模式

原理

发布-订阅/观察者模式又叫做观察者模式,定义对象之中一种一对多的依赖关系,当一个对象的状态发生改变时候,所有依赖于它的对象都将得到通知。

优点:

  • 1.统一管理

订阅模式中,可以抽离出调度中心单独成一个文件,可以对一系列的订阅事件进行统一管理。

  • 2.松耦合

发布者不需要知道订阅者的数量,订阅者听得话题或者订阅者是通过什么方式运行的。他们能够相互独立地运行,这样就可以让你分开开发这两部分而不需要担心对状态或实现的任何细微的影响。

缺点:

  • 1.状态未知

发布者不知道订阅者的状态,反之亦然,这样的话,你根本不知道在另一端是否会没有问题?

  • 2.不能识别恶意消息

攻击者(恶意的发布者)能够入侵系统并且撕开它。这会导致恶意的消息被发布,订阅者能够获得他们以前并不能获得的消息。

  • 3.关系更新难

更新发布者和订阅者的关系会是一个很难的问题,因为毕竟他们根本不认识对方。

Events 是 Node.js 中一个非常重要的 core 模块, 在 node 中有许多重要的 core API 都是依赖其建立的。比如 Stream 是基于 Events 实现的, 而 fs, net, http 等模块都依赖 Stream。Node.js 中 Events 模块的实现使用了发布-订阅/观察者模式。源码 ➡️ events->EventEmitter

const EventEmitter = require("events");

let emitter = new EventEmitter();

emitter.on("customEvent", () => {
  console.log("customEvent fired");
});

emitter.emit("customEvent");

实现

class EventEmitter {
  constructor() {
    this.events = {};
    this.nextGuid = -1;
  }

  on(type, listener) {
    const events = this.events;
    if (!events.hasOwnProperty(type)) {
      events[type] = {};
    }

    let guid = "uuid_" + ++this.nextGuid;
    events[type][guid] = listener;
    return this;
  }

  once(type, listener) {
    function callback() {
      this.off(type, callback);
      Reflect.apply(listener, this, arguments);
    }
    this.on(type, callback);
    return this;
  }

  off(type, listener) {
    const events = this.events;
    if (!events.hasOwnProperty(type)) {
      return false;
    }

    const handlers = events[type];
    for (let [key, value] of Object.entries(handlers)) {
      if (handlers.hasOwnProperty(key) && listener === value) {
        delete handlers[key];
        return true;
      }
    }
    return false;
  }

  emit(type, ...args) {
    const events = this.events;
    const handlers = events[type];
    for (let listener of Object.values(handlers)) {
      Reflect.apply(listener, this, args);
    }
  }
}

测试结果如下:

let emitter = new EventEmitter();
// on 测试
emitter.on("customEvent", () => {
  console.log("on 测试");
});

// once 测试
emitter.once("customEvent", () => {
  console.log("once 测试");
});

// off 测试
let fn = () => {
  console.log("off 测试");
};
emitter.on("customEvent", fn);
emitter.off("customEvent", fn);

// emit 测试
emitter.emit("customEvent");
setTimeout(() => {
  emitter.emit("customEvent");
}, 1000);

// on 测试
// once 测试
// on 测试