观察者模式与发布订阅模式

观察者模式

定义:

观察者模式是一种行为型设计模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

结构图:

https://zh.wikipedia.org/zh-cn/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F

ES6简易代码实现: 

//ts环境下的es6类模拟,已去掉ts下的类型检查和定义
/**
 * 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
 */
class Observe {
  constructor(name) {
    this.name = name;
  }
  update(payload) {
    console.log(`${this.name}观察的数据发生了变化:${payload}`);
  }
}
class Subject {
  constructor() {
    this.observeList = [];
  }
  addObserve(observe) {
    this.observeList.push(observe);
  }
  removeObserve(observe) {
    this.observeList = this.observeList.filter((item) => item != observe);
  }
  notify(payload) {
    this.observeList.forEach((item) => item.update(payload));
  }
}

ES简易代码实现: 

var Observe = /** @class */ (function () {
    function Observe(name) {
        this.name = name;
    }
    Observe.prototype.update = function (payload) {
        console.log("".concat(this.name, "\u89C2\u5BDF\u7684\u6570\u636E\u53D1\u751F\u4E86\u53D8\u5316\uFF1A").concat(payload));
    };
    return Observe;
}());
var Subject = /** @class */ (function () {
    function Subject() {
        this.observeList = [];
    }
    Subject.prototype.addObserve = function (observe) {
        this.observeList.push(observe);
    };
    Subject.prototype.removeObserve = function (observe) {
        this.observeList = this.observeList.filter(function (item) { return item != observe; });
    };
    Subject.prototype.notify = function (payload) {
        this.observeList.forEach(function (item) { return item.update(payload); });
    };
    return Subject;
}());
var subject = new Subject();
var observe = new Observe("xx");
var observe1 = new Observe("yy");
subject.addObserve(observe);
subject.addObserve(observe1);
subject.notify("zzz");

输出:

观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。 

发布订阅模式

定义:

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。与观察者模式的定义相似,其最大的特点是:发布方与订阅方互不相知,双方都发送自己的消息内容到第三方,由第三方来处理。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

结构图:

观察者模式与发布订阅模式差异

  • 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

  • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

图示:

ES6简易代码实现

//ts环境下的es6类模拟,已去掉ts下的类型检查和定义
class EventEmitter {
  constructor() {
    //事件列表对象
    this.eventList = {};
  }
  //订阅
  on(eventName, fn) {
    //如果对象eventName不存在,先创建一个空数组
    if (!this.eventList[eventName]) {
      this.eventList[eventName] = [];
    }
    //如果事件存在,把fn添加到对应eventList[eventName]列表里面
    this.eventList[eventName].push(fn);
  }
  //取消订阅
  off(eventName, fn) {
    let callbacks = this.eventList[eventName];
    if (!callbacks) return false;
    if (!fn) return callbacks && (callbacks.length = 0);
    //遍历this.eventList[eventName]事件
    for (let i = 0; i < callbacks.length; i++) {
      console.log(callbacks[i].fn, "off------>callbacks[i].fn");
      console.log(callbacks[i], "off-callbacks[i]");
      //判断那个事件==fn,相同则删除
      if (callbacks[i] == fn || callbacks[i].fn == fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
  }

  //监听一次
  once(eventName, fn) {
    // this箭头函数表达式是正则函数表达式的语法紧凑替代方案
    // 它没有自己的this、arguments、super或new.target关键字绑定。
    let on = (...args) => {
      this.off(eventName, on);
      //args代替arguments
      fn.apply(this, args);
    };
    //存储fn,确保单独使用off删除传入的fn时可以被删除掉
    on.fn = fn;
    console.log(on.fn, "once--->on.fn");
    console.log(on, "once-on");
    this.on(eventName, on);
  }
  //发布
  emit(eventName, data) {
    const callbacks = this.eventList[eventName];
    if (!callbacks) return;
    callbacks.forEach((element) => {
      element(data);
    });
  }
}

let events = new EventEmitter();
function test(data) {
  console.log("test is on:" + data);
}
function test1(data) {
  console.log("test1:" + data);
}
function test2(data) {
  console.log("test2:" + data);
}

源码once方法里面的on.fn =fn的作用

once订阅注册的订阅事件名均为on

考虑以下场景:还未发布时订阅者需要取消订阅某个once事件,由于once注册事件函数名均为on,off中cb是无法判断的,因此需要添加on.fn用来标识区分,所以才有off中判断条件为cb||cb.fn

上面的解释不仅说明了once方法里面 on.fn =fn的作用也说明了off方法里面for循环中if判断的后半句callbacks[i].fn == fn。

下面从代码打印中,我们来看没有经过发布,订阅者就取消订阅的once事件

输出语句如下:

events.once("example", test2);
events.off("example", test2);

输出内容:

ES5简易代码实现

//tsc编译js文件
var EventEmitter = /** @class */ (function () {
  function EventEmitter() {
    //事件列表对象
    this.eventList = {};
  }
  //订阅
  EventEmitter.prototype.on = function (eventName, fn) {
    //如果对象eventName不存在,先创建一个空数组
    if (!this.eventList[eventName]) {
      this.eventList[eventName] = [];
    }
    //如果事件存在,把fn添加到对应eventList[eventName]列表里面
    this.eventList[eventName].push(fn);
  };
  //取消订阅
  EventEmitter.prototype.off = function (eventName, fn) {
    var callbacks = this.eventList[eventName];
    if (!callbacks) return false;
    if (!fn) return callbacks && (callbacks.length = 0);
    //遍历this.eventList[eventName]事件
    for (var i = 0; i < callbacks.length; i++) {
      //判断那个事件==fn,相同则删除
      if (callbacks[i] == fn || callbacks[i].fn == fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
  };
  //监听一次
  EventEmitter.prototype.once = function (eventName, fn) {
    var _this = this;
    var on = function () {
      var args = [];
      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
      }
      _this.off(eventName, on);
      //args代替arguments
      fn.apply(_this, args);
    };
    //存储fn,确保单独使用off删除传入的fn时可以被删除掉
    on.fn = fn;
    this.on(eventName, on);
  };
  // 发布
  EventEmitter.prototype.emit = function (eventName, data) {
    var callbacks = this.eventList[eventName];
    if (!callbacks) return;
    callbacks.forEach(function (element) {
      element(data);
    });
  };
  return EventEmitter;
})();
var events = new EventEmitter();
function test(data) {
  console.log("test is on:" + data);
}
function test1(data) {
  console.log("test1:" + data);
}
function test2(data) {
  console.log("test2:" + data);
}
//1
events.on("example", test);
events.emit("example", "dddddd");
//2
events.once("example", test1);
events.emit("example", "true");
//3
events.once("example", test2);
events.emit("example", 123);
//events.off("example", test2);

输出:

参考

JavaScript 发布-订阅模式 - 掘金

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值