在js中發(fā)布訂閱模式和觀察者模式都是非常常用的兩種設(shè)計(jì)模式。兩者也及其相似,初學(xué)者很容易搞混了兩種設(shè)計(jì)模式,下面通過手寫的方式帶大家完整的認(rèn)識(shí)這兩種設(shè)計(jì)模式以及他們之間的區(qū)別
發(fā)布訂閱模式
先大致的分析一下什么是發(fā)布訂閱模式
可以參考一下微信的公眾號(hào)訂閱功能,用戶訂閱了這個(gè)公眾號(hào),公眾號(hào)有了新消息就會(huì)推送給用戶。在這樣一個(gè)流程中,大致就可以分出三個(gè)角色來 用戶充當(dāng)?shù)木褪?strong>訂閱者而公眾號(hào)則是發(fā)布者,那第三個(gè)角色是誰呢? 第三個(gè)角色當(dāng)然就是公眾號(hào)的數(shù)據(jù)來源啦,這個(gè)數(shù)據(jù)來源可以看成一個(gè)中轉(zhuǎn)站。他并不關(guān)心誰訂閱了消息,訂閱了什么消息,他要做的就是存下訂閱者的聯(lián)系方式,當(dāng)對(duì)應(yīng)的消息被發(fā)布了他就通知訂閱者。
知道了他的大概思想之后就可以勾勒出代碼的大致輪廓了
//發(fā)布訂閱容器
let observeEmit = {
eventList: {},//裝載訂閱者所訂閱的事件
//訂閱函數(shù)
on(key, fn) {},
//發(fā)布函數(shù)
emit(key, paylod) {}
}
上述代碼中eventList就是用來存儲(chǔ)訂閱者的聯(lián)系方式的,on方法用來提供給用戶進(jìn)行訂閱emit用來發(fā)布
這兩個(gè)函數(shù)的具體實(shí)現(xiàn)如下
//訂閱函數(shù)
on(key, fn) {
/**
* key 被訂閱的內(nèi)容的key值
* fn 訂閱者傳入的訂閱事件
*/
if (!this.eventList[key]) {
this.eventList[key] = []
}
this.eventList[key].push(fn)
},
//發(fā)布函數(shù)
emit(key, paylod) {
for (let i = 0; i < this.eventList[key].length; i++) {
if (this.eventList){
this.eventList[key][i](paylod)
}
}
},
至此,一個(gè)簡單的發(fā)布訂閱模式就寫好了,主要就是在訂閱的時(shí)候?qū)ey以及對(duì)應(yīng)的回調(diào)函數(shù)存入容器里,然后在發(fā)布某個(gè)消息的時(shí)候去觸發(fā)所有的對(duì)應(yīng)的key的回調(diào)函數(shù)。
下面看看如何使用吧
observeEmit.on('msg', function (content) {
console.log('訂閱msg的人收到最新消息:', content)
})
observeEmit.emit('msg', '我觸發(fā)了發(fā)布,請(qǐng)問訂閱者收到了嗎')
這還只是一個(gè)最簡單版的,我們還可以對(duì)他進(jìn)行功能擴(kuò)展
比如,提供只訂閱一次的api
//只訂閱一次
once(key, fn) {
this.on(key, function (content) {
fn(content)
this.off(key, fn)
})
},
//取消訂閱
off(key, fn) {
if (this.eventList[key]) {
for (let i = 0; i < this.eventList[key].length; i++) {
if (this.eventList[key][i] === fn) {
this.eventList[key][i].splice(i, 1)
}
}
}
}
如果先發(fā)布了消息,用戶再訂閱,我們還可以將事件存儲(chǔ)起來,然后在有人訂閱的時(shí)候?qū)⑺l(fā)布出去
在對(duì)象中定義一個(gè)離線事件的屬性
offLineEventList: {},//離線事件棧,如果當(dāng)前發(fā)布的事件暫無訂閱者,就先保存到此事件棧中,等下次有人訂閱了再將此消息發(fā)布給他
改造一下on和emit
//訂閱函數(shù)
on(key, fn) {
/**
* key 被訂閱的內(nèi)容的key值
* fn 訂閱者傳入的訂閱事件
*/
if (!this.eventList[key]) {
this.eventList[key] = []
}
this.eventList[key].push(fn)
//如果離線數(shù)據(jù)棧中有當(dāng)前訂閱的key,那就將之前發(fā)布的消息發(fā)送給訂閱者
if (this.offLineEventList[key]) {
this.emit(key, this.offLineEventList[key])
}
},
//發(fā)布函數(shù)
emit(key, paylod) {
console.log(arguments)
if (this.eventList[key]) {
for (let i = 0; i < this.eventList[key].length; i++) {
if (this.eventList){
this.eventList[key][i](paylod)
}
}
} else {
//如果沒人訂閱過這個(gè)事件,就將本次發(fā)布內(nèi)容先存在離線事件棧內(nèi)
this.offLineEventList[key] = paylod
}
},
這樣一個(gè)較為完善的發(fā)布訂閱模式就寫好啦。
接下來我們就來看看觀察者模式又是怎么樣的吧
觀察者模式
觀察者模式其實(shí)可以簡單的分為兩個(gè)角色,一個(gè)觀察者和一個(gè)被觀察者。由被觀察者主動(dòng)通知觀察者,告訴他我發(fā)生了變化,你可以做你想做的事情了
下面簡單實(shí)現(xiàn)一下
//觀察者類
class Observe {
constructor(name) {
this.name = name
}
//觀察的數(shù)據(jù)發(fā)生了變化,觸發(fā)函數(shù)
update(payload){
console.log(`${this.name}觀察的數(shù)據(jù)發(fā)生了變化:${payload}`)
}
}
//被觀察的類
class Subject {
constructor() {
this.observeList = []
}
//添加觀察者進(jìn)觀察者數(shù)組
addObserve(observe){
this.observeList.push(observe)
}
//發(fā)生變更通知觀察者
notify(payload){
this.observeList.forEach(observe=>{
observe.update(payload)
})
}
}
const subject = new Subject()
const stu1 = new Observe('小明')
const stu2 = new Observe('小虹')
subject.addObserve(stu1)
subject.addObserve(stu2)
subject.notify('測試一下')
發(fā)布訂閱模式中有三個(gè)角色,而觀察者模式中只有兩個(gè)角色。
發(fā)布訂閱模式由中轉(zhuǎn)站收集訂閱者的訂閱消息,然后發(fā)布者通過中轉(zhuǎn)站來通知到對(duì)應(yīng)的訂閱者,
而觀察者模式則是有被觀察者主動(dòng)收集他自身的觀察者,然后自身發(fā)生變化后主動(dòng)的去通知觀察者,也就是調(diào)用觀察者的某個(gè)函數(shù)。兩者的概念非常相似,但是實(shí)現(xiàn)卻大有不同