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方法。目标对象和观察者耦合在了一起。
- 而发布订阅模式,只有一个角色(即第三方),已经没有发布者和订阅者了,完全由这个第三方完成所有的功能,它是事件名和回调函数之间的对应关系。
