- 原文地址:The Publisher/Subscriber Pattern in JavaScript
- 原文做者:jsmanifest
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:EmilyQiRabbit
- 校對者:weisiwu,Alfxjx
簡寫爲 Pub 和 subjavascript
在本篇文章中,咱們將會學習 JavaScript 的發佈/訂閱模式,而且咱們將能看到,在咱們的 JavaScript 代碼中使用這種設計模式很簡單(但卻很高效)。前端
發佈者/訂閱者模式是一種設計模式,旨在讓開發者設計出不直接互相依賴,但卻能夠互相傳遞信息的高效能動態應用程序。java
這種模式在 JavaScript 中十分常見,它和觀察者模式的工做方式很相近 —— 一個區別是,在觀察者模式中,觀察者直接從它所觀察的實體那裏獲得通知,而在發佈者/訂閱者模式中,訂閱者則經過渠道獲得通知,渠道位於發佈者和訂閱者之間並來回傳遞信息。android
若是想要實現一個發佈者/訂閱者模式,咱們須要一個發佈者、一個訂閱者,以及一些存儲訂閱者所註冊的回調函數的空間。ios
下面,讓咱們一塊兒來看看落實到代碼應該怎麼寫。咱們將會使用一個 factory 函數(你不必定非要使用這個模式)來寫出發佈者/訂閱者模式的的實現。git
咱們要作的第一件事,就是在函數中聲明一個本地變量,用來保存訂閱的回調函數:github
function pubSub() {
const subscribers = {}
}
複製代碼
下面,咱們將會定義 subscribe
方法,它負責在 subscribers
中插入回調函數:後端
function pubSub() {
const subscribers = {}
function subscribe(eventName, callback) {
if (!Array.isArray(subscribers[eventName])) {
subscribers[eventName] = []
}
subscribers[eventName].push(callback)
}
return {
subscribe,
}
}
複製代碼
該段代碼會在添加新的事件訂閱前,檢查訂閱者中是否有對應的事件。若是沒有,將在訂閱者上添加值爲空數組的事件屬性,不然將在對應的事件隊列下入隊新的訂閱回調。設計模式
當 publish
事件被觸發的時候,它將會收到兩個參數:數組
eventName
參數subscribers[eventName]
的回調函數的 data
咱們繼續向下看看代碼是如何實現的:
function pubSub() {
const subscribers = {}
function publish(eventName, data) {
if (!Array.isArray(subscribers[eventName])) {
return
}
subscribers[eventName].forEach((callback) => {
callback(data)
})
}
function subscribe(eventName, callback) {
if (!Array.isArray(subscribers[eventName])) {
subscribers[eventName] = []
}
subscribers[eventName].push(callback)
}
return {
publish,
subscribe,
}
}
複製代碼
在遍歷 subscribers
中的回調函數列表以前,要先檢查對象中該屬性是不是數組類型。若是不是,那麼就認爲這個 eventName
以前並無被註冊過,因此就直接返回了。這一步能夠保證避免潛在的程序報錯。
在這以後,若是程序執行到了 .forEach
這一行,那麼咱們就能夠肯定 eventName
已經被註冊了一個或多個回調函數。程序就能夠繼續保證安全的循環遍歷 subscribers[eventName]
。
程序每讀取到一個回調函數,都將會以 data
爲第二個參數來調用它。
當咱們如此訂閱一個函數的時候,就會發生上述流程。
function showMeTheMoney(money) {
console.log(money)
}
const ps = pubSub()
ps.subscribe('show-money', showMeTheMoney)
複製代碼
若是咱們在之後的某一時刻調用 publish
:
ps.publish('show-money', 1000000)
複製代碼
那麼咱們註冊的 showMeTheMoney
回調函數將會被觸發,money
參數的值爲 1000000
:
function showMeTheMoney(money) {
console.log(money) // result: 10000000
}
複製代碼
這就是發佈/訂閱模式的原理。咱們定義了一個 pubSub
函數,並在函數內將回調函數存儲到本地,並提供了 subscribe
方法註冊回調函數,以及 publish
方法來遍歷並使用數據來調用全部註冊過的回調函數。
可是,這裏還存在一個問題。在真正應用這個模式的時候,若是咱們訂閱了不少回調函數,就可能會遇到內存泄漏,若是不想辦法解決這個問題,這將形成極大的浪費。
因此咱們還須要一個移除訂閱的回調函數的方法,以便在不須要它們的時候能夠刪除。一般的方法是在某處定義一個 unsubscribe
方法。而實現它最便捷的位置就是做爲 subscribe
的返回值,由於在我看來這是最直觀的方法,咱們來看看代碼:
function subscribe(eventName, callback) {
if (!Array.isArray(subscribers[eventName])) {
subscribers[eventName] = []
}
subscribers[eventName].push(callback)
const index = subscribers[eventName].length - 1
return {
unsubscribe() {
subscribers[eventName].splice(index, 1)
},
}
}
const { unsubscribe } = subscribe('food', function(data) {
console.log(`Received some food: ${data}`)
})
// 移除訂閱的回調
unsubscribe()
複製代碼
在這個例子中,咱們須要一個索引。這樣咱們就能確保移除的是正確的回調函數,咱們使用得是 .splice
函數,它能夠經過索引來移除咱們須要移除的數組中的項目。
咱們還能夠這樣寫;可是這樣性能就稍差一些:
function subscribe(eventName, callback) {
if (!Array.isArray(subscribers[eventName])) {
subscribers[eventName] = []
}
subscribers[eventName].push(callback)
const index = subscribers[eventName].length - 1
return {
unsubscribe() {
subscribers[eventName] = subscribers[eventName].filter((cb) => {
// 在新的數組中再也不包含這個回調函數
if (cb === callback) {
return false
}
return true
})
},
}
}
複製代碼
雖然發佈者/訂閱者模式有不少優點,可是同時它也存在災難性的缺點,這可能會讓咱們付出巨大的調試時間成本。
咱們如何知道是否以前已經訂閱了同一個回調函數呢?除非咱們實現一個工具來映射整個列表,不然實在沒法知道,可是這樣的話咱們就要使用 JavaScript 來完成更多的工做了。
在實際應用中過分使用發佈者/訂閱者模式,也讓咱們的代碼更加難以維護。事實是,在這種模式中回調函數之間是解耦的,因此當你在多處都使用了回調函數後,追蹤代碼就變得很是困難。
綜上所述就是本文的所有內容。但願能對你有所幫助!
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。