发布订阅
发布订阅模式中,包含发布者,事件调度中心,订阅者三个角色。其中发布者和订阅者是松散耦合的,互相不关心对方是否存在,他们关注的是事件本身。
举个例子,在现实生活中买房子,我们不一定就要直接跑去各大售楼中心,我们只需要把购买房子的这个需求交给中介公司, 而各大房地产公司也只需要通过中介公司来发布房子信息。这样一来,我们不用关心消息是来自哪个房产公司,我们在意的是是否能够顺利收到消息。当然,为了保证订阅者和发布者都能顺利通信,订阅者和发布者都必须知道这个中介公司。
实现
发布者借用事件调度中心提供的emit方法去发布事件,而订阅者则通过on进行订阅
class EventEmitter {
constructor() {
// 维护事件及监听者
this.listeners = {}
}
/**
* 注册事件监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
on(type, cb) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
}
/**
* 发布事件
* @param {String} type 事件类型
* @param {...any} args 参数列表,把emit传递的参数赋给回调函数
*/
emit(type, ...args) {
if (this.listeners[type]) {
this.listeners[type].forEach(cb => {
cb(...args)
})
}
}
/**
* 移除某个事件的一个监听者
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
off(type, cb) {
if (this.listeners[type]) {
const targetIndex = this.listeners[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.listeners[type].splice(targetIndex, 1)
}
if (this.listeners[type].length === 0) {
delete this.listeners[type]
}
}
}
/**
* 移除某个事件的所有监听者
* @param {String} type 事件类型
*/
offAll(type) {
if (this.listeners[type]) {
delete this.listeners[type]
}
}
}
// 创建事件管理器实例
const ee = new EventEmitter()
// 注册一个chifan事件监听者
ee.on('chifan', function() { console.log('吃饭了,我们走!') })
// 发布事件chifan
ee.emit('chifan')
// 也可以emit传递参数
ee.on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}吃${food}!`) })
ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者
// 测试移除事件监听
const toBeRemovedListener = function() { console.log('我是一个可以被移除的监听者') }
ee.on('testoff', toBeRemovedListener)
ee.emit('testoff')
ee.off('testoff', toBeRemovedListener)
ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了
// 测试移除chifan的所有事件监听
ee.offAll('chifan')
console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了
图解
特点
- 发布订阅模式中,发布者和订阅者没有特殊的约束,他们借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁
观察者
观察者模式和发布订阅模式相比,耦合度高,像Vue的响应式原理就是用的观察者模式。在观察者模式中,只有两个主体,分别的目标对象Subject,观察者Observer。好比与现在没了中介公司,由我们自己去售楼处订阅消息。
- 观察者
Observer实现update方法,供目标对象调用,update中执行自定义的业务代码 - 目标对象
Subject维护自身的观察组数组,当自身发生改变时,通过调用自身的notify方法,依次通知每一个观察者执行update
实现
// 观察者
class Observer {
/**
* 构造器
* @param {Function} cb 回调函数,收到目标对象通知时执行
*/
constructor(cb){
if (typeof cb === 'function') {
this.cb = cb
} else {
throw new Error('Observer构造器必须传入函数类型!')
}
}
/**
* 被目标对象通知时执行
*/
update() {
this.cb()
}
}
// 目标对象
class Subject {
constructor() {
// 维护观察者列表
this.observerList = []
}
/**
* 添加一个观察者
* @param {Observer} observer Observer实例
*/
addObserver(observer) {
this.observerList.push(observer)
}
/**
* 通知所有的观察者
*/
notify() {
this.observerList.forEach(observer => {
observer.update()
})
}
}
const observerCallback = function() {
console.log('我被通知了')
}
const observer = new Observer(observerCallback)
const subject = new Subject();
subject.addObserver(observer);
subject.notify();
个人感觉这里的Subject类似于Vue2中的Dep类,Observer类似于Watcher类。
图解
特点
- 角色很明确,没有事件调度中心作为中间者,目标对象
Subject和观察者Observer都要实现约定的成员方法。 - 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。