谈一谈JS的观察者模式和发布订阅模式

观察者模式

什么是观察者模式呢? 当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。在观察者模式中,主体有两个:观察者和被观察者,两者直接耦合,互相认识,被观察者发生变动,会主动通知观察者。

举个例子说明下

js 复制代码
// 被观察者
class Market {
  constructor() {
    this.observerList = [];
  }
  // 观察者们订阅超市活动
  addObserver(observer) {
    this.observerList.push(observer);
  }
  // 发通知
  notify(task) {
    console.log("楼下超市" + task);
    this.observerList.forEach((observer) => observer.scareBuying());
  }
}

// 观察者
class Observer {
  constructor(name, money) {
    this.name = name;
    this.money = money;
  }

  // 抢购
  scareBuying() {
    console.log(this.name + "抢购" + this.money);
  }
}

// 楼下超市
const market = new Market();
// 楼上的张三,身上有10块
const zhangsan = new Observer("张三", "10块");
// 楼上的李四,身上有20块
const lisi = new Observer("李四", "20块");

// 张三、李四都订阅了超市的活动
market.addObserver(zhangsan);
market.addObserver(lisi);
// 超市主动通知张三、李四有打折活动
market.notify("鸡蛋打折");
market.notify("牛奶打折");

/**
 * 运行结果:
 *
 * 楼下超市鸡蛋打折
 * 张三抢购10块
 * 李四抢购20块
 *
 * 楼下超市牛奶打折
 * 张三抢购10块
 * 李四抢购20块
 */

从以上结果不难看出,超市和观者者张三、李四存在耦合关系,发生打折活动时,能直接通知到本人,这就是观察者模式。

学vue的同志看到这里,是不是有一种莫名的熟悉感?没错,vue的数据劫持也是同样的思想,上代码

js 复制代码
// 被观察者,楼下超市
let targetObj = {
  name: "楼下超市",
  activity: "",
};
// 观察者1,楼上的张三,身上有10块
function observer1() {
  this.name = "张三";
  this.money = "10块";
  console.log(this.name + "抢购" + this.money);
}
// 观察者2,楼上的李四,身上有20块
function observer2() {
  this.name = "李四";
  this.money = "20块";
  console.log(this.name + "抢购" + this.money);
}

// 观察者们订阅超市活动
Object.defineProperty(targetObj, "activity", {
  enumerable: true,
  configurable: true,
  get() {
    return this.value;
  },
  set(val) {
    console.log("楼下超市" + val);
    // 超市主动通知张三、李四有打折活动
    observer1(val);
    observer2(val);
    this.value = val;
  },
});

targetObj.activity = "鸡蛋打折";
targetObj.activity = "牛奶打折";

/**
 * 运行结果:
 *
 * 楼下超市鸡蛋打折
 * 张三抢购10块
 * 李四抢购20块
 *
 * 楼下超市牛奶打折
 * 张三抢购10块
 * 李四抢购20块
 */

发布订阅模式

理解了观察者模式,那什么是发布订阅模式呢? 有些同志会觉得观察者模式和发布订阅模式傻傻分不清,今天就来讲清楚。

发布订阅模式通过一个中介者(通常是事件总线或消息队列)来管理发布者和订阅者之间的关系。

通俗地说,发布订阅模式和观察者模式相比,存在三个主体,如果说超市和消费者两者构成了观察者模式,那么发布订阅模式则是在两者之间多了一个中介,超市和消费者没有耦合关系,也不知道彼此存在,双方都是通过这个中介互通消息。

好,那即然是这样,多的不说,直接看代码

js 复制代码
class PubSub {
  constructor() {
    this.events = {};
  }

  // 订阅消息
  subscribe(type, fn) {
    if (!this.events[type]) {
      this.events[type] = [];
    }
    this.events[type].push(fn);
  }
  // 发布消息
  publish(type, ...args) {
    if (this.events[type]) {
      this.events[type].forEach((fn) => fn(...args));
    }
  }
  // 取消订阅
  unsubscribe(type, fn) {
    if (this.events[type]) {
      delete this.events[type];
    }
  }
}

// 中介公司
let realtor = new PubSub();

// 楼上的张三,身上有100块
function zhangsan() {
  this.name = "张三";
  this.money = 100;
  if (this.money > 10) {
    console.log(this.name + "收到通知,抢购了10块");
    this.money = this.money - 10;
  } else {
    console.log("悲催,余额不足");
  }
}
// 楼上的李四,身上有200块
function lisi() {
  this.name = "李四";
  this.money = 200;
  if (this.money > 20) {
    console.log(this.name + "收到通知,抢购了20块");
    this.money = this.money - 20;
  } else {
    console.log("悲催,余额不足");
  }
}

// 张三向中介公司订阅了鸡蛋打折的消息
realtor.subscribe("eggDiscount", zhangsan);
// 李四向中介公司订阅了鸡蛋打折的消息
realtor.subscribe("eggDiscount", lisi);
// 李四向中介公司订阅了牛奶打折的消息
realtor.subscribe("milkDiscount", lisi);

console.log("中介公司获知楼下超市鸡蛋打折了,开始通知会员");
realtor.publish("eggDiscount");
console.log("中介公司获知楼下超市牛奶打折了,开始通知会员");
realtor.publish("milkDiscount");

/**
 * 运行结果:
 *
 * 中介公司获知楼下超市鸡蛋打折了,开始通知会员
 * 张三收到通知,抢购了10块
 * 李四收到通知,抢购了20块
 *
 * 中介公司获知楼下超市牛奶打折了,开始通知会员
 * 李四收到通知,抢购了20块
 */

从以上代码我们不难发现,超市的打折活动是消费者主动订阅的,未订阅的消费者不会收到通知,发布者和订阅者通过中介间接建立松散耦合,其适用场景对处理多个事件和消息时会更为高效灵活。

看到这里,学习vue的同志又有了一种莫名的熟悉感,好像在哪里见过?继续往下看

vue 组件通信之on emit

js 复制代码
// vue 组件通信之on emit
let eventEmitter = {
  list: {},
  // 订阅
  on(key, fn) {
    if (!this.list[key]) {
      this.list[key] = [];
    }
    this.list[key].push(fn);
  },
  // 发布
  emit() {
    let key = [].shift.call(arguments),
      fns = this.list[key];

    if (!fns || fns.length === 0) {
      return false;
    }
    fns.forEach((fn) => {
      fn.apply(this, arguments);
    });
  },
};

function complete() {
  console.log("父组件接收到了子组件传递到complete事件");
}

function cancel() {
  console.log("父组件接收到了子组件传递到cancel事件");
}
console.log("父组件订阅complete事件");
eventEmitter.on("complete", complete);
console.log("父组件订阅cancel事件");
eventEmitter.on("cancel", cancel);

console.log("子组件发布complete事件");
eventEmitter.emit("complete");
console.log("子组件发布cancel事件");
eventEmitter.emit("cancel");

/**
 *  运行结果:
 *
 * 父组件订阅complete事件
 * 父组件订阅cancel事件
 *
 * 子组件发布complete事件
 * 父组件接收到了子组件传递到complete事件
 *
 * 子组件发布cancel事件
 * 父组件接收到了子组件传递到cancel事件
 */

组件通信之事件总线eventBus

js 复制代码
// 组件通信之事件总线eventBus
import Vue from "vue";
export const EventBus = new Vue();

// 订阅 complete
EventBus.$on("complete", (data) => {
  this.complete();
});
// 订阅 cancel
EventBus.$on("cancel", (data) => {
  this.cancel();
});

// 发布
EventBus.$emit("complete", {
  num: this.num,
  deg: this.deg,
});

// 取消订阅,要及时移除事件监听
EventBus.$off("decreased", {});
相关推荐
古蓬莱掌管玉米的神5 小时前
vue3语法watch与watchEffect
前端·javascript
拉一次撑死狗5 小时前
Vue基础(2)
前端·javascript·vue.js
qq_544329176 小时前
下载一个项目到跑通的大致过程是什么?
javascript·学习·bug
Jane - UTS 数据传输系统9 小时前
VUE+ Element-plus , el-tree 修改默认左侧三角图标,并使没有子级的那一项不展示图标
javascript·vue.js·elementui
ThomasChan12311 小时前
Typescript 多个泛型参数详细解读
前端·javascript·vue.js·typescript·vue·reactjs·js
zzlyx9911 小时前
.NET 9 微软官方推荐使用 Scalar 替代传统的 Swagger
javascript·microsoft·.net
Bunury11 小时前
组件封装-List
javascript·数据结构·list
我命由我1234511 小时前
NPM 与 Node.js 版本兼容问题:npm warn cli npm does not support Node.js
前端·javascript·前端框架·npm·node.js·html5·js
Orange30151112 小时前
【自己动手开发Webpack插件:开启前端构建工具的个性化定制之旅】
前端·javascript·webpack·typescript·node.js
Jacob程序员14 小时前
leaflet绘制室内平面图
android·开发语言·javascript