概念:观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
如何理解这句话呢?来举个生活中的例子
学生小明情绪比较容易波动,所以当小明的情绪发生变化时,父母和老师希望及时获得通知,以便可以采取适当的措施来帮助他。
这样父母和老师就能及时了解小明的情绪状态,当小明情绪低落时,他们可以给予他关心、安慰和支持。
在这个例子中,小明就是被观察者,而父母和老师都是观察者。
下面就来简单实现一下它的代码。
class Subject {
// 被观察者 学生
constructor() {
this.state = "happy";
this.observers = []; // 存储所有的观察者
}
//新增观察者
add(o) {
this.observers.push(o);
}
// 更新状态
setState(newState) {
// 更新状态后通知
this.state = newState;
this.notify();
}
//通知所有的观察者
notify() {
this.observers.forEach((o) => o.update(this));
}
}
class Observer {
// 观察者 父母和老师
constructor(name) {
this.name = name;
}
//通知更新
update(student) {
console.log(`亲爱的${this.name} 通知您当前学生的状态是${student.state}`);
}
}
//创建被观察者学生
let student = new Subject("学生");
//创建观察者父母和老师
let parent = new Observer("父母");
let teacher = new Observer("老师");
//给被观察者学生增加观察者
student.add(parent);
student.add(teacher);
student.setState("sad");
//亲爱的父母 通知您当前学生的状态是sad
//亲爱的老师 通知您当前学生的状态是sad
发布订阅模式跟观察者模式很像,它们其实都有发布者和订阅者,但是他们是有区别的:
为了更好区分这两种设计模式,接着上述例子。
通过发布订阅模式,小明不需要直接告诉每位老师他的情绪状态,而是通过情绪监测系统自动发布消息给所有订阅了他情绪状态的老师。这种发布者不直接接触到订阅者的模式,就是发布订阅模式。
那么发布订阅模式有何应用呢?
Vue的EventBus事件总线其实就是用了发布订阅模式。用法如下:
1.创建全局事件总线
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
2.通过on订阅事件
//组件A
export default{
mounted(){
// 监听事件的触发
this.$bus.$on("sendMsg", data => {
console.log(data)//身体健康
})
},
beforeDestroy(){
// 取消监听
this.$bus.$off("sendMsg")
}
}
3.通过emit发布事件
//组件B
<template>
<button @click="handlerClick">点击发送数据</button>
</template>
export default{
methods:{
handlerClick(){
this.$bus.$emit("sendMsg", "身体健康")
}
}
}
了解了EventBus的使用后,那么接下来就来手动实现一个EventBus。
实现目标:使用 $on 订阅事件,使用 $emit 发布事件。
主要思路:
class EventBus {
constructor() {
// 缓存列表,用来存放注册的事件与回调
this.cache = {};
}
// 订阅事件
on(name, cb) {
// 如果当前事件没有订阅过,就给事件创建一个队列
if (!this.cache[name]) {
this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
}
this.cache[name].push(cb);
}
// 触发事件
emit(name, ...args) {
// 检查目标事件是否有监听函数队列
if (this.cache[name]) {
// 逐个调用队列里的回调函数
this.cache[name].forEach((callback) => {
callback(...args);
});
}
}
}
// 测试
let eventBus = new EventBus();
// 订阅事件
eventBus.on("teacherName1", (pos, state) => {
console.log(`订阅者小陈老师,小明同学当前在${pos},心情状态是${state}`);
});
eventBus.on("teacherName1", (pos, state) => {
console.log(`订阅者小陈老师,小明同学当前在${pos},心情状态是${state}`);
});
eventBus.on("teacherName2", (pos, state) => {
console.log(`订阅者小李老师,小明同学当前在${pos},心情状态是${state}`);
});
// 发布事件
eventBus.emit("teacherName1", "教室", "伤心");
eventBus.emit("teacherName2", "操场", "开心");
输出结果:
实现目标:增加 off 方法取消订阅。
class EventBus {
constructor() {
// 缓存列表,用来存放注册的事件与回调
this.cache = {};
}
// 订阅事件
on(name, cb) {
// 如果当前事件没有订阅过,就给事件创建一个队列
if (!this.cache[name]) {
this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
}
this.cache[name].push(cb);
}
// 触发事件
emit(name, ...args) {
// 检查目标事件是否有监听函数队列
if (this.cache[name]) {
// 逐个调用队列里的回调函数
this.cache[name].forEach((callback) => {
callback(...args);
});
}
}
// 取消订阅
off(name, cb) {
const callbacks = this.cache[name];
const index = callbacks.indexOf(cb);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
}
// 测试
let eventBus = new EventBus();
let event1 = function (...args) {
console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${args}`)
};
let event2 = function (...args) {
console.log(`通知2-订阅者小陈老师,小明同学当前心情状态:${args}`)
};
// 订阅事件
eventBus.on("teacherName1", event1);
eventBus.on("teacherName1", event2);
// 取消订阅事件1
eventBus.off('teacherName1', event1);
// 发布事件
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName2", "教室", "上课", "打架", "愤怒");
输出结果:
实现目标:增加 once 方法只订阅一次。
class EventBus {
constructor() {
// 缓存列表,用来存放注册的事件与回调
this.cache = {};
}
// 订阅事件
on(name, cb) {
// 如果当前事件没有订阅过,就给事件创建一个队列
if (!this.cache[name]) {
this.cache[name] = []; //由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
}
this.cache[name].push(cb);
}
// 触发事件
emit(name, ...args) {
// 检查目标事件是否有监听函数队列
if (this.cache[name]) {
// 逐个调用队列里的回调函数
this.cache[name].forEach((callback) => {
callback(...args);
});
}
}
// 取消订阅
off(name, cb) {
const callbacks = this.cache[name];
const index = callbacks.indexOf(cb);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
// 只订阅一次
once(name, cb) {
// 执行完第一次回调函数后,自动删除当前订阅事件
const wrapper = (...args) => {
cb(args);
this.off(name, wrapper);
};
this.on(name, wrapper);
}
}
// 测试
let eventBus = new EventBus();
let event1 = function (...args) {
console.log(`通知1-订阅者小陈老师,小明同学当前心情状态:${args}`)
};
// 订阅事件,只订阅一次
eventBus.once("teacherName1", event1);
// 发布事件
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
eventBus.emit("teacherName1", "教室", "上课", "打架", "愤怒");
输出结果:
写作不易,你的一赞一评,就是我前行的最大动力。如有问题,欢迎指出!