javascript 設計模式之單例模式javascript
javascript 適配、代理、裝飾者模式的比較github
javascript 設計模式之觀察者模式promise
javascript 設計模式之發佈訂閱者模式markdown
上一篇講到觀察者模式,這篇要講下發布-訂閱模式。 有人可能會說了:這兩個不是一回事嗎?確實這兩個模式的核心思想、運行機制上沒有本質的差異。 但仍是有些差異,要否則我這篇要講啥,且聽我娓娓道來
基於一個主題/事件通道,但願接收通知的對象(稱爲subscriber)經過自定義事件訂閱主題,被激活事件的對象(稱爲publisher)經過發佈主題事件的方式被通知。
還記得上次那個小明、小紅、小二去樓盤買房的事?
小明最近看中了一個樓盤,到了售樓處以後才被告知,該樓盤的房子還未開盤,具體開盤時間還沒定。除了小明,一塊兒來的還有小紅,小二人,爲了第一時間得知樓盤開盤時間,他們都把電話號碼留給了銷售員王五,王五也答應樓盤一開盤就會發消息通知他們。 過了不久,新樓盤開盤時間定了,王五趕忙拿出花名冊,遍歷上面的號碼,羣發了短信通知了他們。 至於這些人收到通知是選擇走路來、開車來、或者不來就自行決定了
這個是觀察者模式。
但若是小明等人沒有去售樓處找五五,而是去該樓盤官網查看,得知還沒開盤關注了該樓盤進展。開盤以後,王五也不會羣發短信,而是將消息同步到官網,這樣官網自動通知了關注該樓盤的意向購房者。 這樣就是發佈訂閱者模式。
重點概念對號入座
發佈者: 王五
訂閱者: 小明、小紅,小二
事件中心: 官網
訂閱事件: 小明、小紅、小二進入官網關注樓盤進展
通知變化: 官網自動通知了關注樓盤的意向購買者
這二者有啥差異呢?
類比官網,事件中心應該要有發事件( emit ),監聽事件( on ),還有銷燬事件監聽者( off ),銷燬指定事件的全部監聽者( offAll ),還有隻觸發一次( once )的功能
class EventEmitter {
constructor() {
// handleEvents 是一個 map ,用於存儲事件與回調之間的對應關係
this.handleEvents = {}
}
/** * 註冊事件監聽者, 它接受事件類型和回調函數做爲參數 * @param {String} type 事件類型 * @param {Function} callback 回調函數 */
on(type, callback) {
// 先檢查一下事件類型有沒有對應的監聽函數隊列
if (!this.handleEvents[type]) {
// 若是沒有,那麼首先初始化一個監聽函數隊列, 不然直接 push 會報錯
this.handleEvents[type] = []
}
// 把回調函數推入事件類型的監聽函數隊列裏去
this.handleEvents[type].push(callback)
}
/** * 發佈事件,它接受事件類型和監聽函數入參做爲參數 * @param {String} type 事件類型 * @param {...any} args 參數列表,把emit傳遞的參數賦給回調函數 */
emit(type, ...args) {
// 檢查事件類型是否有監聽函數隊列
if (this.handleEvents[type]) {
// 若是有,則逐個調用隊列裏的回調函數
this.handleEvents[type].forEach((callback) => {
callback(...args)
})
}
}
/** * 移除某個事件回調隊列裏的指定回調函數 * @param {String} type 事件類型 * @param {Function} callback 回調函數 */
off(type, callback) {
const callbacks = this.handleEvents[type]
const index = callbacks.indexOf(callback)
// 找到則移除
if (index !== -1) {
callbacks.splice(index, 1)
}
// 該事件類型對應的回調函數爲空了,則將該對象刪除
if (callbacks.length === 0) {
delete this.handleEvents[type]
}
}
/** * 移除某個事件的全部回調函數 * @param {String} type 事件類型 */
offAll(type) {
if (this.handleEvents[type]) {
delete this.handleEvents[type]
}
}
/** * 爲事件註冊單次監聽器 * @param {String} type 事件類型 * @param {Function} callback 回調函數 */
once(type, callback) {
// 對回調函數進行包裝,使其執行完畢自動被移除
const wrapper = (...args) => {
callback.apply(args)
this.off(type, wrapper)
}
this.on(type, wrapper)
}
}
複製代碼
測試
// 建立事件管理器實例
const emitter = new EventEmitter()
// 註冊一個refresh事件監聽者
emitter.on('refresh', function () {
console.log('調用後臺獲取最新數據')
})
// 發佈事件refresh
emitter.emit('refresh')
// 也能夠emit傳遞參數
emitter.on('refresh', function (pageNo, pageSize) {
console.log(`調用後臺獲取參數,參數爲:{pageNo:${pageNo},pageSize:${pageSize}}`)
})
// 此時會打印兩條信息,由於前面註冊了兩個refresh事件的監聽者
emitter.emit('refresh', '2', '20')
// 測試移除事件監聽
const toBeRemovedListener = function () {
console.log('我是一個能夠被移除的監聽者')
}
emitter.on('testRemove', toBeRemovedListener)
emitter.emit('testRemove')
emitter.off('testRemove', toBeRemovedListener)
// 此時事件監聽已經被移除,不會再有console.log打印出來了
emitter.emit('testRemove')
// 測試移除refresh的全部事件監聽
emitter.offAll('refresh')
// 此時能夠看到emitter.handleEvents已經變成空對象了,再emit發送refresh事件也不會有反應了
console.log(emitter)
emitter.once('onlyOne', function () {
console.info('只會觸發一次')
})
emitter.emit('onlyOne')
// 不會彈出信息
emitter.emit('onlyOne')
複製代碼
// 訂閱這些函數,當點擊時觸發發佈,會依次觸發這些函數
$('#btn1').click(function () {
alert(1)
})
$('#btn1').click(function () {
alert(2)
})
$('#btn1').click(function () {
alert(3)
})
複製代碼
promise 就是觀察者模式的經典場景: 先將 then 裏面的函數儲存起來,在 resovle 與 reject 裏取出函數,並執行函數。 其實就是收集依賴->觸發通知->取出依賴執行的方式 具體能夠看手寫 promise
// 自定義事件,自定義回調
var callbacks = $.Callbacks() // 注意大小寫, C 爲大寫
callbacks.add(function (info) {
console.info('fn1', info)
})
callbacks.add(function (info) {
console.info('fn2', info)
})
callbacks.add(function (info) {
console.info('fn2', info)
})
callbacks.fire('come')
callbacks.fire('go')
複製代碼
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', (name) => {
// 監聽 some 事件
console.info('some event is occured 1 ' + name)
})
emitter1.on('some', () => {
// 監聽 some 事件
console.info('some event is occured 2')
})
// 觸發 some 事件
// 並傳遞參數
emitter1.emit('some', 'zhangsan')
複製代碼
觀察者模式:
發佈訂閱者模式:
看的出來,發佈訂閱者模式更加高級,由於它更加鬆散,沒有耦合,那是否是現實中發佈訂閱者模式用的更多呢?實際上不是的。由於在實際開發中,咱們的模塊解耦訴求並非要求它們徹底解耦,若是兩個模塊之間自己存在關聯,且這種關聯是穩定的、必要的,那這時就應該用觀察者模式便可。
你的點贊是對我最大的確定,若是以爲有幫助,請留下你的讚揚,謝謝!!!