观察者模式和发布订阅模式
通常两种模式可看做一样的。都是一种对象间一对多的关系,当一个对象(被观察者/发布者)改变时,会通知所有的(观察者/订阅者)对象。
例1
比如:我们去商店买东西,而商店没货;我们留下联系方式,等老板有货的时候通知我们。 看一个简单的实现:
const Dep = { //发布者仓库
subs: [], // 记录订阅者的信息
addSub(sub) { // 增加一个订阅者
if (!this.subs.includes(sub)) { // 判重
this.subs.push(sub)
}
},
unSub(sub) { // 取消订阅
if (!this.subs.includes(sub)) return;
const idx = this.subs.indexOf(sub)
this.subs.splice(idx, 1)
},
notify() { // 发布(将信息发送给订阅者)
for (const sub of this.subs) {
sub.update()
}
}
}
const customer1 = {
update() {
console.log('customer1XX')
}
}
const customer2 = {
update() {
console.log('customer2XX')
}
}
Dep.addSub(customer2);
Dep.addSub(customer1);
Dep.notify()
例2
再比如:我们去租房子,一般通过中介来匹配房源。中介记录我们的租房需求,比如,带阳台单间、两室套...;房东发布信息给中介,中介等有对应的房源了再通知我们去看房。我们各种各样的租房需求可以看成一个类型。
class Publisher {
constructor() {
this.subsMap = {}
}
/* 消息订阅 */
subscribe(type, cb) {
if (this.subsMap[type]) {
if (!this.subsMap[type].includes(cb))
this.subsMap[type].push(cb)
} else this.subsMap[type] = [cb]
}
/* 消息退订 */
unsubscribe(type, cb) {
if (!this.subsMap[type] ||
!this._subsMap[type].includes(cb)) return
const idx = this.subsMap[type].indexOf(cb)
this.subsMap[type].splice(idx, 1)
}
/* 消息发布 */
notify(type, ...payload) {
if (!this.subsMap[type]) return
this.subsMap[type].forEach(cb => cb(...payload))
}
}
const publish = new Publisher()
publish.subscribe('type1', message => console.log(message)) // 订阅type1
publish.subscribe('type2', message => console.log(message))
publish.notify('type1', ' type1有了...') // 通知订阅了type1类型的订阅对象
publish.notify('type2', ' type2有了...') // 通知订阅了type2类型的订阅对象
两者区别
上述两种方式,前者商店和顾客是互相知道的,后者房东和租户是不知道对方的存在通过中介来进行通信。前者为观察者模式,后者是发布订阅模式。 借用网上一张图表示为:
- 观察者模式 中的观察者和被观察者之间还存在耦合;
- 发布 - 订阅模式 中的发布者和订阅者通过消息代理来进行通信,解耦更加彻底
vue中的eventBus和响应式原理都是此模式在实际项目中的应用。
bus的使用
//main
const bus = new Vue()
Vue.prototype.$bus = bus;
//组件1
this.$bus.$on("myEvent", args => { console.log(args) })
//组件2
this.$bus.$emit("myEvent", 'msg')
响应式化大致就是使用 Object.defineProperty 把数据转为 getter/setter,初始化时在getter里添加订阅。在setter时执行发布的过程