观察者模式
什么是观察者模式呢? 当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。在观察者模式中,主体有两个:观察者和被观察者,两者直接耦合,互相认识,被观察者发生变动,会主动通知观察者。
举个例子说明下
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", {});