1.观察者模式
Observer Design Pattern:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知,并自动更新。
// 目标对象类
class Subject {
constructor(){
this.observers = [];
}
// 添加观察者
add(o){
this.observers.push(o);
}
// 移除观察者
remove(o){
const index = this.observers.find(f=>f===o);
if(index !== -1){
this.observers.splice(index,1);
}
}
// 通知
notify(...args){
this.observers.forEach(observer=>{
observer.update(...args);
})
}
}
// 观察者类
class Observer {
// 更新
update(...args){
console.log('目标对象的状态变化了,我需要更新',args)
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.add(observer1);
subject.add(observer2);
subject.notify('new state');
2.发布订阅模式
object/array方式实现
// 全局事件总线 EventEmitter
// 推荐阅读FaceBook推出的通用EventEmiiter库的源码(https://github.com/facebookarchive/emitter)
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件名与回调之间的对应关系
this.handlers = {};
}
// 用于安装事件监听器,它接受事件名和回调函数作为参数
on(eventName, cb) {
if (!this.handlers[eventName]) {
this.handlers[eventName] = [];
}
this.handlers[eventName].push(cb);
}
// 用于移除某个事件回调队列里的指定回调函数
off(eventName, cb) {
if (!this.handlers[eventName]) {
return;
}
const index = this.handlers[eventName].findIndex(f => f === cb);
this.handlers[eventName].splice(index, 1);
}
// 用于触发目标事件,它接受事件名和监听函数入参作为参数
emit(eventName, ...args) {
if (!this.handlers[eventName]) {
return;
}
this.handlers[eventName].forEach((cb) => {
cb(...args);
});
}
// 为事件注册单次监听器
once(eventName, cb) {
// 对回调函数进行包装,使其执行完毕后自动被移除
const wrapper = (...args) => {
cb.apply(null, args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
}
map/set方式实现
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件名与回调之间的对应关系
this.handlers = new Map();
}
// 用于安装事件监听器,它接受事件名和回调函数作为参数
on(eventName, cb) {
if (!this.handlers.has(eventName)) {
this.handlers.set(eventName, new Set());
}
this.handlers.get(eventName).add(cb);
}
// 用于移除某个事件回调队列里的指定回调函数
off(eventName, cb) {
if (!this.handlers.has(eventName)) {
return;
}
this.handlers.get(eventName).delete(cb);
}
// 用于触发目标事件,它接受事件名和监听函数入参作为参数
emit(eventName, ...args) {
if (!this.handlers.has(eventName)) {
return;
}
this.handlers.get(eventName).forEach((cb) => {
cb(...args);
});
}
// 为事件注册单次监听器
once(eventName, cb) {
// 对回调函数进行包装,使其执行完毕后自动被移除
const wrapper = (...args) => {
cb.call(null, ...args);
this.off(eventName, wrapper);
};
this.on(eventName, wrapper);
}
}
案例
在公众号消息卡片跳转小程序的某个详情页的时候,比如此时认证信息过期或者用户没有登录过,获取详情数据的接口调用失败,此时会重定向到登录页,登录成功后,在跳转回详情页,但是详情页已经加载完成,因此不会再重新发请求获取详情信息,如何解决?
我们的解决方案是在详情页初始化时,订阅一个登录成功的事件,对应的回调函数是获取详情页数据,然后在登录成功的时候,触发登录成功的事件。
3.两者区别
- 对于观察者模式,一定是有两个角色(即目标对象类和观察者类),目标对象和观察者是直接产生联系的,因为观察者就在目标对象的数组里,这样才能保证当目标对象的状态发生变化时,能够调用所有观察者的update方法。目标对象和观察者耦合在了一起。
- 而发布订阅模式,只有一个角色(即第三方),已经没有发布者和订阅者了,完全由这个第三方完成所有的功能,它是事件名和回调函数之间的对应关系。