特点:这种模式一般会定义一个主体和众多的个体,这里主体可以想象为一个消息中心,里面有各种各样的消息,众多的个体可以订阅不同的消息,当未来消息中心发布某条消息的时候,订阅过它的个体就会得到通知。
1、定义消息中心
首先,我们定义一个消息中心,里面包含存储消息的_msg对象,返回一个对象,里面有3个方法属性分别用于订阅一个消息,发布消息和取消订阅的消息。
// 消息中心
const msgCenter = (function() {
let _msg = {} // 存储消息
return {
// 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
register: function (type, fn) { },
// 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
fire: function (type, args) { },
// 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
cancel: function (type, fn) { }
}
})()
一个简单的观察者模式的框架就搭建出来了,我们最重要的工作就是实现这3个方法,
2、定义个体的构造函数和个体的方法
// 个体构造函数
function Person () {
// 存储的是个体所订阅过的消息类型
this.alreadyRegister = {}
}
// 每个个体都有的一些方法,放到原型链上,订阅消息
Person.prototype.register = function (type, fn) {
// this.alreadyRegister[type]存在说明之前已经订阅过了
if (this.alreadyRegister[type]) {
console.log('您已经订阅过该消息,请不要重复订阅!')
} else {
msgCenter.register(type, fn)
// 在个体内再另存一份
this.alreadyRegister[type] = fn
}
}
// 取消订阅
Person.prototype.cancel = function (type) {
msgCenter.cancel(type, this.alreadyRegister[type])
delete this.alreadyRegister[type]
}
在订阅消息时,要保证消息中心有一份信息,个体里面也会有一份备份
3、实现效果
let person1 = new Person()
let person2 = new Person()
let person3 = new Person()
person1.register('playInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person1.register('workInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person2.register('playInfo', function (e) {
console.log(`person2得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('playInfo', function (e) {
console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('workInfo', function (e) {
console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
msgCenter.fire('playInfo', {info: '刘亦菲女神!'})
msgCenter.fire('workInfo', {info: '互联网冰河世纪!'})
想要实现的效果是:当消息中心发布某个类型的消息,订阅了该类型的个体就会得到该消息(收到通知),并且,消息要传到个体的register方法的fn参数中。
4、实现消息中心的方法
// 消息中心
const msgCenter = (function () {
let _msg = {} // 存储消息
return {
// 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
register: function (type, fn) {
// 目的让_msg变成这样的结构{ 'playInfo':[person1.alreadyRegister.playInfo,person2.alreadyRegister.playInfo...] },这样就用数组存储了哪些个体订阅了该类型的消息
// 因为数组里存放着所有订阅者的回调函数,换句话说根本不用管哪些个体订阅了,一旦发布了该类型的新消息,直接无脑执行数组里所有的回调函数即可让所有订阅的个体收到新消息
if (_msg[type]) {
_msg[type].push(fn)
} else {
_msg[type] = [fn]
}
},
// 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
fire: function (type, args) {
// 如果type不存在说明压根就没有这个消息,更没有人订阅(也就是没有数组和fn),没人订阅你发布给谁看呢?直接return不做处理
if (!_msg[type]) {
return
}
const event = {
type: type,
args: args || {}
}
for (let i = 0;i < _msg[type].length;i++) {
// 把新消息传递给所有订阅者的回调函数fn,然后调用函数
_msg[type][i](event)
}
},
// 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
cancel: function (type, fn) {
if (!_msg[type]) {
return
}
// 遍历,找到数组中的fn,然后删除,这样就实现了取消订阅的功能
for (let i = 0;i < _msg[type].length;i++) {
if (_msg[type][i] === fn) {
_msg[type].splice(i, 1)
break
}
}
}
}
})()
5、验证
下面再验证一下重复订阅以及取消订阅:
// 重复订阅
person1.register('playInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
console.log('————————————')
person1.cancel('playInfo')
msgCenter.fire('playInfo', {info: '胡歌男神!'})
完整代码
// 消息中心
const msgCenter = (function () {
let _msg = {} // 存储消息
return {
// 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
register: function (type, fn) {
// 目的让_msg变成这样的结构{ 'playInfo':[person1.alreadyRegister.playInfo,person2.alreadyRegister.playInfo...] },这样就用数组存储了哪些个体订阅了该类型的消息
// 因为数组里存放着所有订阅者的回调函数,换句话说根本不用管哪些个体订阅了,一旦发布了该类型的新消息,直接无脑执行数组里所有的回调函数即可让所有订阅的个体收到新消息
if (_msg[type]) {
_msg[type].push(fn)
} else {
_msg[type] = [fn]
}
},
// 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
fire: function (type, args) {
// 如果type不存在说明压根就没有这个消息,更没有人订阅(也就是没有数组和fn),没人订阅你发布给谁看呢?直接return不做处理
if (!_msg[type]) {
return
}
const event = {
type: type,
args: args || {}
}
for (let i = 0;i < _msg[type].length;i++) {
// 把新消息传递给所有订阅者的回调函数fn,然后调用函数
_msg[type][i](event)
}
},
// 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
cancel: function (type, fn) {
if (!_msg[type]) {
return
}
// 遍历,找到数组中的fn,然后删除,这样就实现了取消订阅的功能
for (let i = 0;i < _msg[type].length;i++) {
if (_msg[type][i] === fn) {
_msg[type].splice(i, 1)
break
}
}
}
}
})()
// 个体构造函数
function Person () {
// 存储的是个体所订阅过的消息类型
this.alreadyRegister = {}
}
// 每个个体都有的一些方法,放到原型链上,订阅消息
Person.prototype.register = function (type, fn) {
// this.alreadyRegister[type]存在说明之前已经订阅过了
if (this.alreadyRegister[type]) {
console.log('您已经订阅过该消息,请不要重复订阅!')
} else {
msgCenter.register(type, fn)
// 在个体内再另存一份
this.alreadyRegister[type] = fn
}
}
// 取消订阅
Person.prototype.cancel = function (type) {
msgCenter.cancel(type, this.alreadyRegister[type])
delete this.alreadyRegister[type]
}
let person1 = new Person()
let person2 = new Person()
let person3 = new Person()
person1.register('playInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person1.register('workInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person2.register('playInfo', function (e) {
console.log(`person2得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('playInfo', function (e) {
console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('workInfo', function (e) {
console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
msgCenter.fire('playInfo', {info: '刘亦菲女神!'})
msgCenter.fire('workInfo', {info: '互联网冰河世纪!'})
// 重复订阅
person1.register('playInfo', function (e) {
console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
console.log('————————————')
person1.cancel('playInfo')
msgCenter.fire('playInfo', {info: '胡歌男神!'})