观察者模式
说起观察者模式,离不开发布者和订阅者 。
举个生活中的例子:刷微博
我们会在微博中关注一些博主,当这些博主要更新内容时,会将内容发布到平台上,平台再将内容推送给我们。
我们就是订阅者,博主则是发布者。
使用场景
组件通信、消息监听、
代码中的实现
简易版的观察者模式
// 发布者类
class Publisher {
constructor(name) {
this.name = name;
this.observers = []
}
// 增加订阅者
add(observer) {
this.observers.push(observer);
}
// 取消订阅
remove(observer) {
const index = this.observers.findIndex(o => o === observer);
this.observers.splice(index, 1);
}
// 发布消息
notify() {
this.observers.forEach(observer => {
observer.do(this.name);
});
}
}
// 订阅者类
class Observe {
constructor(name) {
this.name = name;
}
do(name) {
console.log(`${this.name}又看到${name}的新瓜啦!`)
}
}
const yxfan = new Observe('yxfan');
const xiaopa = new Observe('xiaopa');
const krisWu = new Publisher('吴亦凡');
krisWu.add(yxfan);
krisWu.add(xiaopa);
krisWu.notify();
yxfan和xiaopa订阅了krisWu的动态,所以当krisWu发布新瓜时,两位订阅者就能接受到。
所以打印:yxfan又看到吴亦凡的新瓜啦!`` xiaopa又看到吴亦凡的新瓜啦!
原谅我不厚道的笑了。。。
实现Event Emitter
Emitter在开发中经常用(典型的事件发布者-订阅者模式),不管是Vue合适React,涉及到组件通信时,用Emitter将会方便许多(父子组件通信大可不必)。
下面实现一个简易版的Emitter
class Emitter {
// 事件对象
handlers = {}
// 添加事件
on(eventName, cb) {
if (this.handlers[eventName]) {
this.handlers[eventName].push(cb);
} else {
this.handlers[eventName] = [cb];
}
}
// 触发事件
emit(eventName, ...params) {
if (this.handlers[eventName]) {
return this.handlers[eventName].forEach(cb => {
cb(...params);
});
}
throw `${eventName}事件不存在`
}
// 删除事件
remove(eventName) {
if (this.handlers[eventName]) {
return delete this.handlers[eventName];
}
throw `${eventName}事件不存在`
}
// 单次监听器,用完即删除
once(eventName, cb) {
this.on(eventName, (...args) => {
cb(...args);
this.remove(eventName);
});
}
}
const emitter = new Emitter();
window.emitter = emitter;
window.emitter.on('emit-test', () => {
console.log(`中国又夺冠🏆啦`)
});
window.emitter.on('emit-test', (num) => {
console.log(`现在金牌${num}枚`)
});
try {
window.emitter.emit('emit-test', 15);
console.log(emitter)
window.emitter.remove('emit-test');
window.emitter.emit('emit-test', 15);
} catch (e) {
console.error(e)
}
打印结果:
中国又夺冠🏆啦
现在金牌15枚
Emitter {
handlers: {}
}
emit-test事件不存在
原理很简单,从始至终只需将Emitter实例化一次,并挂载在window上,使得任何地方都可以访问到。
监听时只需要把事件名丢进缓存池handles中,做一个eventName和cb的映射,触发时找到缓存池中的eventName并运行该映射下的方法即可。
观察者模式与发布者-订阅者模式
上述例子中分别实现了观察者模式和发布者-订阅者模式,好像两者并没有什么区别。
上述模式都是为了实现模块间的解耦,但
观察者模式又没有完全解耦。被观察者必须去维护一套观察者的集合,这些观察者必须实现统一的方法供被观察者调用,两者之间还是有着说不清、道不明的关系。
我们用上述两个例子来分析这段话
1、被观察者必须去维护一套观察者的集合
krisWu.add(yxfan); krisWu.add(xiaopa); 被观察者krisWu是知道它的观察者有哪些的,并调用add()把观察者yxfan和xiaopa添加进了自己的观察者集合observers中。
2、观察者必须实现统一的方法供被观察者调用
观察者yxfan和xiaopa中都有do() 在被观察者krisWu的notify()中调用。
而发布者-订阅者就没有那么多戏了,发布者并不知道它的订阅者有哪些,事件的注册和触发都发生在第三方平台上(事件总线),实现了完全的解耦。
区分这两者模式,就看有木有用到第三方平台。