《JavaScript設計模式與開發實踐》讀書筆記。javascript
發佈-訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關係。當一個對象的狀態發生改變時,全部依賴它的對象都將獲得通知。java
例如:在segmentfault咱們關注了某一個問題,這個時候能夠說是訂閱了這個問題的消息。當該問題有了新的回答、評論的時候,segmentfault系統就會遍歷關注了這個問題的用戶,一次給用戶發消息。算法
如今看看如何一步步實現發佈-訂閱模式。segmentfault
首先,指定好發佈者(如 segmentfault 的 問題系統)設計模式
接着,給發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者(問題系統的記錄表)緩存
最後,當發佈者發佈消息的時候,會遍歷緩存列表,依次觸發裏面的回調函數(遍歷記錄表,逐個發消息)數據結構
咱們還能夠在回調函數裏面加入一些參數,訂閱者能夠接收這些參數,進行各自的處理。app
var sgQuestionSystem = {}; // 定義segmentfault的問題系統 /* * 緩存列表 * clientList: { * key: [ * id: <int>, // 惟一標識 * fn: null // 存放回調函數 * ] * } * */ sgQuestionSystem.clientList = {}; /* * 添加訂閱者(訂閱函數),將訂閱的類型與回調函數加入緩存列表 * key: 消息的類型 * id: 訂閱的惟一標識 * fn: 訂閱的回調函數 */ sgQuestionSystem.listen = function(key, id, fn) { if(!this.clientList[key]) { // 若緩存列表沒有該類型的消息,給該類消息初始化 this.clientList[key] = [] } this.clientList[key].push({ // 將訂閱的id, 回調函數添加到對應的消息列表裏 id: id, fn: fn }) } // 發佈消息(發佈函數), 依次通知訂閱者 sgQuestionSystem.trigger = function () { var key = Array.prototype.shift.call(arguments), // 取出消息類型 fns = this.clientList[key]; // 取出該消息對應的回調函數集合 if(!fns || fns.length == 0) { // 若訂閱列表沒有該類型的回到函數,則返回 return false; } for(var i = 0; i< fns.length; i++) { fns[i].fn.apply(this, arguments); // arguments是發佈消息時附送的參數,去掉了key } }
如今,咱們來進行一些簡單的測試:數據結構和算法
// 張三訂閱問題A sgQuestionSystem.listen('questionA', 3, function(questionTitle, content) { console.log('張三您在早前訂閱了問題:questionA'); console.log('現' + questionTitle + '有了新動態'); console.log('內容爲:' + content); }); // 李四訂閱問題A sgQuestionSystem.listen('questionB', 4, function(questionTitle, content) { console.log('李四您在早前訂閱了問題:questionB'); console.log('現' + questionTitle + '有了新動態'); console.log('內容爲:' + content); }) // 問題系統發佈消息 sgQuestionSystem.trigger('questionA', '問題A', '王五回答了問題A'); sgQuestionSystem.trigger('questionB', '問題B', '吳六回答了問題B');
至此,咱們實現了一個簡單的發佈-訂閱模式,訂閱者能夠訂閱本身感興趣的事件了。函數
上部分,咱們實現了一個問題系統的發佈-訂閱模式,如今,咱們要實現一個文章的發佈-訂閱模式,這時候,該怎麼辦?將上面的代碼ctrl + c, ctrl + v, 再改下名字?仍是有更好的解決方案?
答案顯然是有的,咱們能夠將發佈-訂閱功能模塊提取出來,放在一個單獨的對象裏面:
var publishSubscribeEvent = { /* * 緩存列表 * clientList: { * key: [ * id: <int>, // 惟一標識 * fn: null // 存放回調函數 * ] * } * */ clientList: {}, /* * 添加訂閱者(訂閱函數),將訂閱的類型與回調函數加入緩存列表 * key: 消息的類型 * id: 訂閱的惟一標識 * fn: 訂閱的回調函數 */ listen: function(key, id, fn) { if(!this.clientList[key]) { this.clientList[key] = [] } this.clientList[key].push({ id: id, fn: fn }) }, // 發佈消息(發佈函數), 依次通知訂閱者 trigger: function () { var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if(!fns || fns.length == 0) { return false; } for(var i = 0; i< fns.length; i++) { fns[i].fn.apply(this, arguments); } } }
再定義一個安裝發佈-訂閱的函數installPublishSubscribeEvent,這個函數能夠給全部對象都動態安裝發佈-訂閱功能:
var installPublishSubscribeEvent = function(obj) { for(var i in publishSubscribeEvent) { obj[i] = publishSubscribeEvent[i]; } }
再來測試一番,咱們給文章對象 sgArticleSystem 動態添加發布-訂閱功能:
var sgArticleSystem = {}; installPublishSubscribeEvent(sgArticleSystem ); // 張三訂閱文章A動態 sgArticleSystem.listen('articleA', 3, function(articleTitle, content) { console.log('張三您在早前訂閱了文章:articleA'); console.log('現' + articleTitle+ '有了新動態'); console.log('內容爲:' + content); }); // 李四訂閱文章B動態 sgArticleSystem.listen('articleB', 4, function(articleTitle, content) { console.log('李四您在早前訂閱了文章:articleB'); console.log('現' + articleTitle+ '有了新動態'); console.log('內容爲:' + content); }); // 文章系統發佈消息 sgArticleSystem.trigger('articleA', 'JavaScript設計模式之發佈-訂閱模式', '做者修改了文章'); sgArticleSystem.trigger('articleB', 'JavaScript設計模式之策略模式', '王五用戶評論了該文章');
好了,該代碼通過自測是沒有什麼問題的,要是各位看官發現問題,歡迎反饋。如今,咱們已經能夠給咱們指定的對象安裝發佈-訂閱模式,可是,是否是還少了點什麼功能呢?
答案就是少了取消訂閱事件的功能。好比張三忽然不想關注該問題的更新動態了,爲了不繼續收到問題系統推送過來的消息,張三須要取消以前訂閱的事件。如今,咱們給 publishSubscribeEvent 對象增長 remove 方法。
publishSubscribeEvent.remove = function(key, id) { var fns = this.clientList[key]; if(!fns) { // 若是key對應的消息沒人訂閱,直接返回 return false; } if(!id) { // 若是沒傳具體的惟一標識,則取消key的全部對應消息 fns && (fns.length = 0); } else { for(var l = fns.length - 1; l >=0; l--) { var _id = fns[l].id; if(_id == id) { fns.splice(l, 1); // 刪除訂閱者的回調函數 } } } } // 測試代碼 var sgArticleSystem = {}; installPublishSubscribeEvent(sgArticleSystem ); // 張三的訂閱 sgArticleSystem.listen('articleA', 3, function(articleTitle, content) { console.log('張三您在早前訂閱了文章:articleA'); console.log('現' + articleTitle+ '有了新動態'); console.log('內容爲:' + content); }); // 李四的訂閱 sgArticleSystem.listen('articleA', 4, function(articleTitle, content) { console.log('李四您在早前訂閱了文章:articleA'); console.log('現' + articleTitle+ '有了新動態'); console.log('內容爲:' + content); }); sgArticleSystem.remove('articleA', 3); // 刪除張三的訂閱 sgArticleSystem.trigger('articleA', 'JavaScript設計模式之發佈-訂閱模式', '做者修改了文章');
上面的代碼跟原著有所不一樣,原著是在刪除訂閱的時候是用對比回調函數的,而我是往緩存列表加了一個惟一的標識,用於識別。
至此,咱們的發佈-訂閱模式第一部分已完結,歡迎你們收藏評論。
附:
JavaScript設計模式之發佈-訂閱模式(觀察者模式)-Part2
JavaScript數據結構和算法系列:
JS 棧
JS 隊列-優先隊列、循環隊列
JavaScript設計模式系列:
JavaScript設計模式之策略模式