发布-订阅模式广泛应用于异步编程中,这是一种替代传递回调函数的方案。
现实中的例子:
小明想买房,到了售楼处被告知,该楼盘的房子早已售罄。售楼MM告诉小明,不久后还有一些尾盘推出,时间还未定。
小明记下了售楼处的电话,以后每天都会打电话询问是否可购买。除了小明,还有小红、小强也会每天向售楼处咨询这个问题。可能售楼处每天回答 1000个相同内容的电话。
当然现实中销售公司会一一将客户的电话记录下来,新楼盘推出的时候,售楼 MM会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们。
发布 - 订阅模式的作用
购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者会通知这些消息订阅者。
购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留在售楼处,售楼处不关心购房者的任何情况。 而售楼处的变动也不会影响购买者,比如售楼 MM 离职,这些改变都跟购房者无关,只要售楼处记得发短信这件事情。
发布订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另一个对象的某个接口,让两个对象低耦合地联系在一起。
其实只要我们曾经在DOM上绑定过事件,就已经用过此模式:
document.body.addEventListener('click', () => {
alert('hello world')
})
document.body.click()
以下用发布-订阅模式,写以下购房者与售楼处的故事
let event = {
clientList: {},
listen (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push(fn) // 订阅的消息添加进缓存列表
},
trigger (type, money) {
let fns = this.clientList[type]
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false
}
fns.forEach(fn => {
fn.apply(this, [money])
})
}
}
// 再定义一个installEvent函数,用于给所有对象动态安装发布-订阅功能
// 如:另一家售楼处也想要这个功能,就可以调用这个注册了,不同再写多一次这段代码
let installEvent = obj => {
for (let i in event) {
obj[i] = event[i]
}
}
// 给售楼处对象salesOffices动态增加发布-订阅功能
let salesOffices = {}
installEvent(salesOffices)
// 小明订阅信息
salesOffices.listen('squareMeter88', price => {
console.log('小明,你看中的88平方的房子,价格=' + price)
})
// 小光订阅信息
salesOffices.listen('squareMeter88', price => {
console.log('小光,你看中的88平方的房子,价格=' + price)
})
// 小红订阅信息
salesOffices.listen('squareMeter100', price => {
console.log('小红,你看中的100平方的房子,价格=' + price)
})
salesOffices.trigger('squareMeter88', 2000000)
salesOffices.trigger('squareMeter100', 2500000)
运行结果:
小明,你看中的88平方的房子,价格=2000000
小光,你看中的88平方的房子,价格=2000000
小红,你看中的100平方的房子,价格=2500000
总结
发布 — 订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布 — 订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。 从架构上来看,无论是 MVC还是 MVVM,都少不了发布 — 订阅模式的参与,而且JavaScript本身也是一门基于事件驱动的语言。
缺点
当然,发布 — 订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布 — 订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug不是件轻松的事情。