1、发布订阅模式定义
发布订阅模式也叫观察者模式,它定义了对象中一种一对多的依赖关系,当对象的一个状态发生改变时,所有依赖于它的对象都将得到通知。
发布订阅模式可以广泛的运用在异步编程中,可以取代回掉函数,比如我们可以监听Ajax的error和success事件。或者我们想在动画完成每一帧后执行一些事件,那我们可以订阅这个事件,在动画每一帧结束后发布这个事件,在异步编程中使用发布于订阅模式,我们不必过多关心对象在异步期间的状态,而只需订阅感兴趣的事件发生点。
发布订阅模式可以取代对象之间硬编码的通知机制,一个对象不再显示的调用另外一个对象的接口。发布订阅模式将两个对象松耦合的联系在一起,它们不必清除的知道对方的细节。当有新的订阅者出现时,发布者不需要做任何修改,也不会影响到之前的订阅者,只要事先约定的事件名没有发生变化,就可以自由地改变他们。
2、实现发布订阅
- 首先指定发布者
- 然后给发布者添加一个缓存列表,用于存放回掉函数,以便通知订阅者
- 在发布消息的时候遍历这个缓存列表,触发订阅者的回调函数
let publisher = {} // 发布者
publisher.clientList = [] // 缓存列表,用于存放订阅者的回调函数
publisher.listen = function (fn) {
this.clientList.push(fn)
}
publisher.trigger = function () {
for (let i = 0; i < this.clientList.length; i++) {
const fn = this.clientList[i]
fn.apply(this, arguments)
}
// for(let i = 0, fn; fn = )
}
publisher.listen(function (price, data) {
console.log(price)
console.log(data)
})
publisher.listen(function (price, data) {
console.log(price)
console.log(data)
})
publisher.trigger(20000, 183)
这样的发布订阅模式存在一个问题,即所有订阅者都会接收到相同的消息,即使订阅者对此类消息不感兴趣。
改进:
let publisher = {}
publisher.clientList = {}
publisher.listen = function (key, fn) { // key表示订阅者想要接收消息的关键字
if (!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push(fn)
}
publisher.trigger = function () {
let key = [].shift.call(arguments)
let fns = this.clientList[key]
if (!fns || fns.length == 0) {
return false
}
for(let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
publisher.listen('88', function (price, data) { // 该订阅者只接受key为88的消息
console.log('88',price)
console.log(data)
})
publisher.listen('100', function (price, data) {
console.log(price)
console.log(data)
})
publisher.trigger('88', 20000, 183) // 发布key为88的消息
3、通用的发布订阅模式
在上面我们只针对某一个具体的对象添加了发布订阅模式,如果我们想要为其他对象添加发布订阅模式就需要把这样的代码再重新写一次,为了降低代码的耦合性,我们把发布订阅的功能抽取出来,封装为一个对象,然后通过某个方法把发布订阅模式的功能添加到我们想要的对象上,这样就降低了代码的耦合性。
// 首先抽取发布订阅的功能
let event = {
clientList: {},
listen: function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trigger: function () {
let key = [].shift.call(arguments)
let fns = this.clientList[key]
if (!fns || fns.length == 0) {
return false
}
for(let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
}
// 定义一个函数,给对象绑定发布订阅模式
let installEvent = function (obj) {
for (const i in event) {
if (event.hasOwnProperty(i)) {
obj[i] = event[i]
}
}
}
// 使用
let publisher = {}
installEvent(publisher)
publisher.listen('88', function (price, data) {
console.log('88',price)
console.log(data)
})
publisher.listen('100', function (price, data) {
console.log(price)
console.log(data)
})
publisher.trigger('88', 20000, 183)