觀察者模式又叫發佈---訂閱模式,它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知。javascript
舉一個現實生活中的例子,例如小紅在淘寶的一家店裏看上了一雙紅色的鞋,小李也在這家店裏面看上了一頂黑色的帽子,可是聯繫賣家時,賣家回答這兩樣都沒貨了。賣家告訴小紅小李,要是喜歡的話,能夠關注下店鋪,到貨了,我會給你們通知的。這就是一個典型的發佈-訂閱模式,賣家是發佈者,買家是訂閱者。當貨來的時候,會依次通知小紅、小李等買家,依次給他們發消息通知。html
var goodsObj = {}; goodsObj.list = []; //存放訂閱者 //訂閱者 goodsObj.subscribe = function(key,fn){ if(!this.list[key]){ this.list[key] = []; } //訂閱的消息存放到緩存列表中 this.list[key].push(fn); } //取消訂閱 goodsObj.unsubscribe = function(key,fn){ var fns = this.list[key]; //若是key對應的消息沒有訂閱過的話,則返回 if(!fns){ return false; } //若是沒有傳入具體的回調函數,表示取消key對應消息的全部訂閱 if(!fn){ fns && (fns.length = 0); }else{ for(var i = fns.length - 1; i >= 0; i--){ if(fns[i] === fn){ //刪除訂閱者的回調函數 fns.splice(i,1); } } } } //發佈者 goodsObj.publish = function(){ var key = Array.prototype.shift.call(arguments); var fns = this.list[key]; //若是沒有訂閱過該key的消息,直接返回 if(!fns || fns.length === 0){ return false; } for(var i = 0,fn; fn = fns[i++];){ fn.apply(this,arguments); } } //小紅訂閱了鞋 goodsObj.subscribe('shoes',fn1 = function(color){ console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了'); }); //小李訂閱了帽子 goodsObj.subscribe('cap',fn2 = function(size){ console.log('訂閱帽子的尺寸:' + size + '到貨了,能夠拍了'); }); //發佈 goodsObj.publish('shoes','red'); goodsObj.publish('cap',40); //取消訂閱 goodsObj.unsubscribe('shoes',fn1); //發佈消息,看是否能收到 goodsObj.publish('shoes','block');
爲了優化取消訂閱,在訂閱的時候,給每一個訂閱者一個不一樣的標識,代碼以下:java
var goodsObj = {}; goodsObj.list = {}; //存放訂閱者 var subuid = -1; //每一個訂閱者標識不同 //訂閱者 goodsObj.subscribe = function(key,fn){ if(!this.list[key]){ this.list[key] = []; } //訂閱的消息存放到緩存列表中 var token= (++subuid).toString(); this.list[key].push({ token:token, fn:fn }); return token; } //取消訂閱 goodsObj.unsubscribe = function(token){ for(var m in goodsObj.list){ if(goodsObj.list.hasOwnProperty(m)){ for(var i = 0, j = goodsObj.list[m].length; i < j; i++ ){ if(goodsObj.list[m][i].token == token){ goodsObj.list[m].splice(i,1); break; } } } } } //發佈者 goodsObj.publish = function(){ var key = Array.prototype.shift.call(arguments); var fns = this.list[key]; //若是沒有訂閱過該key的消息,直接返回 if(!fns || fns.length === 0){ return false; } for(var i = 0,obj; obj = fns[i++];){ obj.fn.apply(this,arguments); } } //小紅訂閱了鞋 var token1 = goodsObj.subscribe('shoes',function(color){ console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了'); }); //小李訂閱了帽子 goodsObj.subscribe('cap',function(size){ console.log('訂閱帽子的尺寸:' + size + '到貨了,能夠拍了'); }); //發佈 goodsObj.publish('shoes','red'); goodsObj.publish('cap',40); //取消訂閱 goodsObj.unsubscribe(token1); //發佈消息,看是否能收到 goodsObj.publish('shoes','block');
封裝一個全局的發佈-訂閱對象,代碼以下:設計模式
var Event = (function(){ var list = {}, //存放訂閱者 subscribe, unsubscribe, publish, subuid = -1; //每一個訂閱者標識不同 //訂閱者 subscribe = function(key,fn){ if(!list[key]){ list[key] = []; } //訂閱的消息存放到緩存列表中 var token= (++subuid).toString(); list[key].push({ token:token, fn:fn }); return token; }; //取消訂閱 unsubscribe = function(token){ for(var m in list){ if(list.hasOwnProperty(m)){ for(var i = 0, j = list[m].length; i < j; i++ ){ if(list[m][i].token == token){ list[m].splice(i,1); break; } } } } }; //發佈者 publish = function(){ var key = Array.prototype.shift.call(arguments); var fns = list[key]; //若是沒有訂閱過該key的消息,直接返回 if(!fns || fns.length === 0){ return false; } for(var i = 0,obj; obj = fns[i++];){ obj.fn.apply(this,arguments); } }; return { publish:publish, subscribe:subscribe, unsubscribe:unsubscribe } })(); //小紅訂閱了鞋 var token1 = Event.subscribe('shoes',function(color){ console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了'); }); //小李訂閱了帽子 Event.subscribe('cap',function(size){ console.log('訂閱帽子的尺寸:' + size + '到貨了,能夠拍了'); }); //發佈 Event.publish('shoes','red'); Event.publish('cap',40); //取消訂閱 Event.unsubscribe(token1); //發佈消息,看是否能收到 Event.publish('shoes','block');
咱們使用上面封裝的全局的發佈-訂閱對象來實現兩個模塊之間的通訊問題;好比如今有一個頁面有一個按鈕,每次點擊此按鈕後,div中會顯示此按鈕被點擊的總次數;以下代碼:緩存
<button id="clickBtn">點擊我計數</button> <div id="showCount">0</div>
JS代碼以下:app
(function(){ var count = 0; //發佈(責處理點擊操做、發佈消息) document.getElementById('clickBtn').addEventListener('click',function(){ Event.publish('add',count++); },false); //訂閱(負責監聽add這個消息,並把點擊的總次數顯示到頁面上來) Event.subscribe('add',function(curCount){ document.getElementById('showCount').innerHTML = curCount; }) })();
訂閱發佈更精簡代碼:函數
var Event = function(){ var listen,obj,remove,one,trigger,__this; obj = {}; __this = this; listen = function(key,eventfn){ var _ref,stack; stack = (_ref = obj[key]) != null ? _ref : obj[key] = []; return stack.push(eventfn); }; one = function(key,eventfn){ remove(key); return listen(key,eventfn); }; remove = function(key){ var _ref; return (_ref = obj[key]) != null ? _ref.length = 0 : void 0; }; trigger = function(){ var fn,stack,_i,_len,_ref,key; key = Array.prototype.shift.call(arguments); stack = (_ref = obj[key]) != null ? _ref :obj[key] = []; for(_i=0,_len=stack.length;_i<_len;_i++){ fn = stack[_i]; if(fn.apply(__this,arguments) === false){ return false; } } } return { listen:listen, one:one, remove:remove, trigger:trigger } } var addTv = Event(); addTv.listen('play',function(data){ console.log('今天上午要播放的電影是:' +data.name); }); addTv.one('play',function(data){ console.log('只有我訂閱的電影信息:' +data.name); }); addTv.trigger('play',{'name':'國產凌凌漆'}); addTv.remove('play'); addTv.trigger('play',{'name':'國產凌凌漆22'});