谈一谈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", {});
相关推荐
Mintopia3 分钟前
🌐 实时翻译 + AIGC:Web跨语言内容生成的技术闭环
前端·javascript·aigc
前端开发爱好者4 分钟前
Vite+ 获得 1250万美元的 A 轮融资,生态加速!
前端·javascript
Larcher4 分钟前
JS 变量声明避坑指南:彻底搞懂 var/let/const 的 3 大核心区别与最佳实践
前端·javascript·node.js
拖拉斯旋风11 分钟前
0基础学习Openai之:通过Prompt生成你心中的那幅画🎨
javascript·openai
梵得儿SHI1 小时前
Vue 数据绑定深入浅出:从 v-bind 到 v-model 的实战指南
前端·javascript·vue.js·双向绑定·vue 数据绑定机制·单向绑定·v-bind v-model
Moment1 小时前
Electron 发布 39 版本 ,这更新速度也变态了吧❓︎❓︎❓︎
前端·javascript·node.js
自由日记1 小时前
前端学习:选择器的类别
前端·javascript·学习
江城开朗的豌豆1 小时前
Webpack打包:从“庞然大物”到“精致小可爱”
前端·javascript
JS.Huang2 小时前
【JavaScript】构造函数与 new 运算符
开发语言·javascript·原型模式