一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
前言
观察者模式(observer)和发布订阅者模式(Publisher--Subscribe)的区别到底是什么?常常会搞混,因为他们的差别很细微
区别
如上图所示:
- 耦合度差异
- 观察者模式 中的观察者和被观察者之间还存在
耦合,被观察者还是知道观察者的; - 发布-订阅模式 中的发布者和订阅者不需要知道对方的存在,他们通过消息代理来进行通信,
解耦更加彻底;
- 关注点不同:
- 观察者模式主要是实现观察者和被观察者的
- 发布-订阅模式则主要是实现调度中心的
使用场景
观察者模式
例如:群发消息
观察者模式比较难理解的点就是到底谁是被观察者,比如在这个群发消息里面,比如一个群里面有小A,小B,小C,现在小A在群里发送一个消息,那么谁是观察者,谁是被观察者,这里面观察者者,小A(因为小A也会看到消息更新),小B,小C,被观察者是消息对话框, 接下来我们用代码来实现一下这个场景
class Subject {
constructor() {
this.observes = [];
}
// 添加观察者
addObserves(observe) {
this.observes.push(observe);
}
// 通知观察者
notify(message) {
this.observes.forEach((observe) => {
observe.receiveNotification(message);
})
}
}
class Observe {
constructor(name) {
this.name = name;
}
receiveNotification(message) {
console.log(`${this.name}收到来自---${message}`);
}
}
// 定义观察者
const A = new Observe('小A');
const B = new Observe('小B');
const C = new Observe('小C');
// 定义被观察者
const dialogWindow = new Subject();
dialogWindow.addObserves(A);
dialogWindow.addObserves(B);
dialogWindow.addObserves(C);
dialogWindow.notify('小A发送的消息');
发布订阅模式
例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。
例如 Vue中的双向绑定
他们都有一个共同的特点,就相当于实现了一个平台。比如youtube上的订阅功能,youtube相当于实现了一个发布订阅功能,订阅者只需要点击订阅功能,作者只需要发布作品,youtube就会通知订阅者作者发布了新视频,而这里的youtube平台就是实现了发布订阅模式
实现
实现双向绑定
let data = { name: 'my name', age: 12 };
observe(data);
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach((key) => {
dataChange(data,key,data[key]);
})
}
function dataChange(data, key, value) {
observe(value);
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
return value;
},
set: function (newValue) {
console.log('data发生了变化', newValue);
value = newValue;
}
})
}
实现EventEmitter
class EventEmitter{
constructor() {
// 存储事件的数据结构,为了方便查找,使用对象(字典)
this._cache = {};
}
// 绑定
on(eventName, callback) {
// 将同一类型事件放到一个数组中
// 如果eventName存在
let fns = this._cache[eventName];
if (fns && Array.isArray(fns)) {
if (!fns.includes[callback]) {
fns.push(callback);
}
}else {
fns = [callback];
}
this._cache[eventName] = fns;
// 支持链式调用
return this;
}
emit(eventName, data) {
let fns = this._cache[eventName];
if (Array.isArray(fns)) {
fns.forEach((fn) => {
fn(data);
})
}
return this;
}
// 解绑
off(eventName, callback) {
// 解绑如果传入callback,那就是解绑eventName的callback方法;如果不传callback,那就是解绑eventName的全部方法
let fns = this._cache[eventName];
if (Array.isArray(fns)) {
if (callback) {
let index = fns.indexOf(callback)
if (index !== -1) {
fns.splice(index, 1);
}
} else {
fns.length = 0;
}
}
return this;
}
}
const event1 = new EventEmitter();
event1.on('input', function input(data) {
console.log('输入'+data);
});
event1.on('output', function output(data) {
console.log('输出'+data);
});
console.log(event1);
event1.emit('input', 1) // 输入1
event1.emit('output',2) // 输出2