摘要:發布訂閱的作用發布訂閱模式可以廣泛應用與異步編程中,這是一種替代傳統回調函數的方案。發布訂閱模式讓兩個對象松散耦合地聯系在一起,雖然不清楚彼此的細節,但不影響它們之間的通信。
發布-訂閱模式又叫觀察者模式,它定義與對象間的一種一對多的關系,當一個對象的狀態發生改變時,所有依賴與它的對象都將得到通知。發布訂閱的作用
發布-訂閱模式可以廣泛應用與異步編程中,這是一種替代傳統回調函數的方案。
比如訂閱ajax的succ,error等事件,只需訂閱事件發生點,而無需關注異步運行期間狀態
發布-訂閱可以取代對象之間硬編碼的通信機制,一個對象不再顯式地調用另外一個對象的某個接口。
發布-訂閱模式讓兩個對象松散耦合地聯系在一起,雖然不清楚彼此的細節,但不影響它們之間的通信。
當有新的訂閱者出現時,發布者的代碼不需要任何修改;同樣發布者改變時,也不會影響到之前的訂閱者。
發布-訂閱模式的通用實現var event = { //緩存列表,存放訂閱者的回調函數 clientList: [], //增加訂閱者, key:事件名,fn:回調函數 listen: function(key, fn){ if(!this.clientList[key]){ this.clientList[key] = [] } this.clientList[key].push(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, fn; fn = fns[i++];) { fn.apply(this, arguments) } }, //取消訂閱 remove: function(key, fn) { var fns = this.clientList[key] // 如果對應的消息沒人訂閱,直接返回 if(!fns) { return false } // 如果沒有傳入回調函數,表示取消key對應消息的所有訂閱 if(!fn){ fns && (fns.length = 0) } else { for(var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l] if (_fn === fn) { fns.splice(l, 1) } } } } } var salesOffices = {} // 給對象安裝發布-訂閱功能 var installEvent = function(obj){ for(var i in event){ obj[i] = event[i] } } installEvent(salesOffices) salesOffices.listen("squareMeter88", fn1 = function(price){ console.log("價格1:"+price) }) salesOffices.listen("squareMeter87", fn2 = function(price){ console.log("價格2:"+price) }) salesOffices.trigger("squareMeter88", 20000) // 價格1: 20000 salesOffices.remove("squareMeter88") salesOffices.trigger("squareMeter88", fn1) // 打印結果為空
其實在這個發布-訂閱模式中還存在兩個問題:
給對象多帶帶添加其實是一種資源浪費
發布者與訂閱者存在一定耦合性,至少要知道salesOffices這個對象
所以接下來實現一個全局的發布-訂閱對象
var Event = (function(){ var clientList = {}, listten, trigger, remove; listen = function(key, fn){ if(!clientList[key]){ clientList[key] = [] } clientList[key].push(fn) } trigger = function(){ var key = Array.prototype.shift.call(arguments) fns = clientList[key] if(!fns || fns.length === 0) { return false } for(var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments) } }, remove = function(key, fn) { var fns = clientList[key] if(!fns) { return false } if(!fn){ fns && (fns.length = 0) } else { for(var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l] if (_fn === fn) { fns.splice(l, 1) } } } } return { listen, trigger, remove } })() Event.listen("squareMeter88", fn1 = function(price){ // 訂閱消息 console.log("價格1:"+price) }) Event.trigger("squareMeter88", 20000) // 價格1: 20000必須先訂閱在發布嗎
我們所了解的訂閱發布都是訂閱者必須先訂閱一個消息,隨后才能接受到發布者發布的消息。如果順序反過來,發布者先發布一條消息,而此時沒有訂閱者,無疑消息會消失。
探討下這種需求在某些情況下,我們需要先講這條消息保存下來,等到有對象訂閱它的時候,再重新把消息發布給訂閱者,如果QQ中的離線消息,離線消息先被保存在服務器中,接收人下次登陸之后,可以從新接收到這條消息。
再假如一個商城系統中,獲取到用戶信息后才能渲染導航模塊,無疑這是一個異步的過程,不能保證ajax何時返回。所以我們需要我們發布訂閱對象擁有先發布后訂閱的能力。
如何實現呢為了滿足這種需要,我們要建立一個存放離線事件的堆棧,當事件發布的時候,如果此時還沒有訂閱者來訂閱這個事件,我們暫時把發布事件的動作包裹在一個函數里,這些包裹函數被存入堆棧中,等到終于有對象來訂閱此事件,我們將遍歷堆棧并且依次執行這些包裝函數,也就是重新發布里面的事件。當然離線事件的生命周期只有一次,就像QQ未讀消息只會被重新閱讀一次。
命名沖突全局的訂閱-發布對象里只有一個clientList來存放消息名和回調函數,我們都通過它來訂閱和發布各種消息,久而久之,難免會出現事件名沖突的情況,所以需要給Event對象提供創建命名空間的功能。
具體實現
var Event = (function() { var Event, _default = "default"; Event = function() { var _listen, _trigger, _remove, _shift = [].shift, _unshift = [].unshift, namespaceCache = {}, _create, each = function(arr, fn) { var ret; for (var i = 0, l = arr.length; i < l; i++) { var n = arr[i]; ret = fn.call(n, i, n); } return ret; }; _listen = function(key, fn, cache) { if (!cache[key]) { cache[key] = []; } cache[key].push(fn); }; _remove = function(key, cache, fn) { if (cache[key]) { if (fn) { for (var i = cache[key].length - 1; i >= 0; i--) { if (cache[key][i] === fn) { // 刪除訂閱回調函數 cache[key].splice(i, 1); } } } else { cache[key] = []; } } }; _trigger = function() { var cache = _shift.call(arguments); var key = _shift.call(arguments); var args = arguments; var _this = this; var stack = cache[key]; if (!stack || !stack.length) { return; } return each(stack, function() { return this.apply(_this, args); }) }; _create = function(namespace) { namespace = namespace || _default; var cache = {}; var offlineStack = []; //離線事件 var ret = { listen: function(key, fn, last) { _listen(key, fn, cache); if (offlineStack === null) { return; } if (last === "last") { offlineStack.length && offlineStack.pop()(); } else { each(offlineStack, function() { this(); }); } offlineStack = null; }, one: function(key, fn, last) { _remove(key, cache); this.listen(key, fn, last); }, remove: function(key, fn) { _remove(key, cache, fn); }, trigger: function() { var fn, args, _this = this; _unshift.call(arguments, cache); args = arguments; fn = function() { return _trigger.apply(_this, args); }; if (offlineStack) { return offlineStack.push(fn); } return fn(); } }; return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret; }; return { create: _create, one: function(key, fn, last) { var event = this.create(); event.one(key, fn, last); }, remove: function(key, fn) { var event = this.create(); event.remove(key, fn); }, listen: function(key, fn, last) { var event = this.create(); event.listen(key, fn, last); }, trigger: function() { var event = this.create(); event.trigger.apply(this, arguments); } } }(); return Event; }()); var fn1 = function(price) { console.log(price); }; // 實例 Event.listen("squareMeter88", fn1); Event.remove("squareMeter88", fn1); Event.listen("squareMeter88", function(price) { console.log("fn2: " + price); }); Event.trigger("squareMeter88", 20000); // fn2: 20000總結 優點
時間與對象之間的解耦,應用非常廣泛,既可以用在異步編程中,也可以幫助我們完成更松耦合的代碼編寫,從架構上看,無論MVC還是MVVM都少不了發布-訂閱的參與
缺點創建訂閱者本身要消耗一定時間和內存,無論消息是否發生,一直存在內存中。雖然發布-訂閱模式雖然可以弱化對象之間的聯系,但是過度使用,對象之間的必然聯系會被深埋,特別是多個訂閱者和發布者嵌套會造成難以追蹤的bug。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108683.html
摘要:缺點不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。預防低水平人員帶來的風險。開閉原則,高拓展性。這里的訂閱者稱為觀察者,而被觀察者稱為發布者,當一個事件發生,發布者會發布通知所有訂閱者,并常常以事件對象形式傳遞消息。 介紹 最近開始給自己每周訂個學習任務,學習結果反饋為一篇文章的輸出,做好學習記錄。 這一周(02.25-03.03)我定的目標是《JavaScript 模式》...
摘要:發布通過回調方法向發布事件。觀察者一個回調函數的集合,它知道如何去監聽由提供的值。 本文目錄 一、項目起步 二、編寫路由組件 三、編寫頁面組件 1.編寫單一組件 2.模擬數據 3.編寫主從組件 四、編寫服務 1.為什么需要服務 2.編寫服務 五、引入RxJS 1.關于RxJS 2.引入RxJS 3.改造數據獲取方式 六、改造組件 1.添...
摘要:設計模式與開發實踐讀書筆記。發布訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關系。附設計模式之發布訂閱模式觀察者模式數據結構和算法系列棧隊列優先隊列循環隊列設計模式系列設計模式之策略模式 《JavaScript設計模式與開發實踐》讀書筆記。 發布-訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關系。當一個對象的狀態發生改變時,所有依賴它的對象都將得到通知。 例...
摘要:發布訂閱模式訂閱者把自己想訂閱的事件注冊到調度中心,當發布者發布該事件到調度中心,也就是該事件觸發時,由調度中心統一調度訂閱者注冊到調度中心的處理代碼。 發布-訂閱模式,看似陌生,其實不然。工作中經常會用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。他們都使用了發布-訂閱模式,讓開發變得更加高效方便。 一...
閱讀 2843·2021-11-25 09:43
閱讀 2493·2021-10-09 09:44
閱讀 2806·2021-09-22 15:49
閱讀 2582·2021-09-01 11:43
閱讀 2551·2019-08-30 14:16
閱讀 471·2019-08-29 17:24
閱讀 3027·2019-08-29 14:00
閱讀 1391·2019-08-29 13:05