學習和總結文章同步發佈於 https://github.com/xianshanna...,有興趣能夠關注一下,一塊兒學習和進步。
首先咱們須要瞭解二者的定義和實現的方式,才能更好的區分二者的不一樣點。html
或許之前認爲訂閱發佈模式是觀察者模式的一種別稱,可是發展至今,概念已經有了很多區別。前端
在 軟件架構中, 發佈-訂閱是一種 消息 範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不一樣的類別,無需瞭解哪些訂閱者(若是有的話)可能存在。一樣的,訂閱者能夠表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(若是有的話)存在。
或許你用過 eventemitter
、node 的 events
、Backbone 的 events
等等,這些都是前端早期,比較流行的數據流通訊方式,即訂閱發佈模式。node
從字面意思來看,咱們須要首先訂閱,發佈者發佈消息後纔會收到發佈的消息。不過咱們還須要一箇中間者來協調,從事件角度來講,這個中間者就是事件中心,協調發布者和訂閱者直接的消息通訊。git
完成訂閱發佈整個流程須要三個角色:github
以事件爲例,簡單流程以下:數組
發佈者->事件中心<=>訂閱者,訂閱者須要向事件中心訂閱指定的事件 -> 發佈者向事件中心發佈指定事件內容 -> 事件中心通知訂閱者 -> 訂閱者收到消息(多是多個訂閱者),到此完成了一次訂閱發佈的流程。架構
簡單的代碼實現以下:app
class Event { constructor() { // 全部 eventType 監聽器回調函數(數組) this.listeners = {} } /** * 訂閱事件 * @param {String} eventType 事件類型 * @param {Function} listener 訂閱後發佈動做觸發的回調函數,參數爲發佈的數據 */ on(eventType, listener) { if (!this.listeners[eventType]) { this.listeners[eventType] = [] } this.listeners[eventType].push(listener) } /** * 發佈事件 * @param {String} eventType 事件類型 * @param {Any} data 發佈的內容 */ emit(eventType, data) { const callbacks = this.listeners[eventType] if (callbacks) { callbacks.forEach((c) => { c(data) }) } } } const event = new Event() event.on('open', (data) => { console.log(data) }) event.emit('open', { open: true })
Event 能夠理解爲事件中心,提供了訂閱和發佈功能。dom
訂閱者在訂閱事件的時候,只關注事件自己,而不關心誰會發布這個事件;發佈者在發佈事件的時候,只關注事件自己,而不關心誰訂閱了這個事件。函數
觀察者模式定義了一種一對多的依賴關係,讓多個 觀察者對象同時監聽某一個目標對象,當這個目標對象的狀態發生變化時,會通知全部 觀察者對象,使它們可以自動更新。
觀察者模式咱們可能比較熟悉的場景就是響應式數據,如 Vue 的響應式、Mbox 的響應式。
觀察者模式有完成整個流程須要兩個角色:
簡單流程以下:
目標<=>觀察者,觀察者觀察目標(監聽目標)-> 目標發生變化-> 目標主動通知觀察者。
簡單的代碼實現以下:
/** * 觀察監聽一個對象成員的變化 * @param {Object} obj 觀察的對象 * @param {String} targetVariable 觀察的對象成員 * @param {Function} callback 目標變化觸發的回調 */ function observer(obj, targetVariable, callback) { if (!obj.data) { obj.data = {} } Object.defineProperty(obj, targetVariable, { get() { return this.data[targetVariable] }, set(val) { this.data[targetVariable] = val // 目標主動通知觀察者 callback && callback(val) }, }) if (obj.data[targetVariable]) { callback && callback(obj.data[targetVariable]) } }
可運行例子以下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover" /> <title></title> </head> <body> <div id="app"> <div id="dom-one"></div> <br /> <div id="dom-two"></div> <br /> <button id="btn">改變</button> </div> <script> /** * 觀察監聽一個對象成員的變化 * @param {Object} obj 觀察的對象 * @param {String} targetVariable 觀察的對象成員 * @param {Function} callback 目標變化觸發的回調 */ function observer(obj, targetVariable, callback) { if (!obj.data) { obj.data = {} } Object.defineProperty(obj, targetVariable, { get() { return this.data[targetVariable] }, set(val) { this.data[targetVariable] = val // 目標主動通知觀察者 callback && callback(val) }, }) if (obj.data[targetVariable]) { callback && callback(obj.data[targetVariable]) } } const obj = { data: { description: '原始值' }, } observer(obj, 'description', value => { document.querySelector('#dom-one').innerHTML = value document.querySelector('#dom-two').innerHTML = value }) btn.onclick = () => { obj.description = '改變了' } </script> </body> </html>
角色角度來看,訂閱發佈模式須要三種角色,發佈者、事件中心和訂閱者。二觀察者模式須要兩種角色,目標和觀察者,無事件中心負責通訊。
從耦合度上來看,訂閱發佈模式是一個事件中心調度模式,訂閱者和發佈者是沒有直接關聯的,經過事件中心進行關聯,二者是解耦的。而觀察者模式中目標和觀察者是直接關聯的,耦合在一塊兒(有些觀念說觀察者是解耦,解耦的是業務代碼,不是目標和觀察者自己)。
優缺點都是從前端角度來看的。
因爲訂閱發佈模式的發佈者和訂閱者是解耦的,只要引入訂閱發佈模式的事件中心,不管在何處均可以發佈訂閱。同時訂閱發佈者相互之間不影響。
訂閱發佈模式在使用不當的狀況下,容易形成數據流混亂,因此纔有了 React 提出的單項數據流思想,就是爲了解決數據流混亂的問題。
靈活是有點,同時也是缺點,使用不當就會形成數據流混亂,致使代碼很差維護。
訂閱發佈模式須要維護事件列隊,訂閱的事件越多,內存消耗越大。
目標變化就會通知觀察者,這是觀察者最大的有點,也是由於這個優勢,觀察者模式在前端纔會這麼出名。
相比訂閱發佈模式,因爲目標和觀察者是耦合在一塊兒的,因此觀察者模式須要同時引入目標和觀察者才能達到響應式的效果。而訂閱發佈模式只須要引入事件中心,訂閱者和發佈者能夠再也不一處。