釋出-訂閱者模式又叫觀察者模式,它定義對象間的一種一對多的依賴關系,當一個對象的狀态發生改變時,所有依賴于它的對象都将得到通知。那具體的含義,就是有訂閱者和釋出者,兩者的功能,訂閱是請求在某些事件(event)到達時可以通知它并執行對應的動作(action),而釋出則相對的是向訂閱告知事件(event)已經到達,你可以執行對應的動作(action)了。
舉一個現實中的例子。我們在現實中可以看到一個現象:假如A要去買房子,那麼這個A就會把自己的聯系方式給售樓處的負責人,隻要樓盤有房子就會通知A,然後A就可以過去看房子,買房子,就不需要每天都去打電話詢問是不是到了購買時間,這個例子就可以利用釋出-訂閱者模式進行實作,這個售樓處負責人是釋出者,A是訂閱者,接下來我們實作簡單的釋出-訂閱者模式。
代碼如下:
var salesOffices = {};//定義售樓處
salesOffices.clientList = [];//緩存清單,存放訂閱者的回調函數
//增加訂閱者
salesOffices.listen = function ( fn ){
this.clientList.push( fn ); //訂閱的消息存進緩存清單
};
//釋出消息
salesOffices.trigger = function (){
for(var i = 0, fn; fn = this.clientList[ i++ ];){
fn.apply(this,arguments);
}
};
//A訂閱消息
salesOffices.listen( function(price, squareMeter){
console.log('價格=' + price);
console.log('squareMeter=' + squareMeter);
});
//B訂閱消息
salesOffices.listen( function(price, squareMeter){
console.log('價格=' + price);
console.log('squareMeter=' + squareMeter);
});
salesOffices.trigger(20000,88);//價格=20000 squareMeter=88
salesOffices.trigger(30000,110); //價格=30000 squareMeter=110
以上代碼實作了最簡單的釋出-訂閱模式,但是這裡還存在一些問題。我們看到訂閱者接收到了釋出者釋出的每個消息,雖然A隻想買88平方米的房子,但是釋出者把110平方米的資訊也發給了A,這對于小明來說是不必要的,是以我們需要增加一個辨別key,讓訂閱者隻訂閱自己感興趣的消息。修改之後的代碼如下:
var salesOffices = {};//定義售樓處
salesOffices.clientList = [];//緩存清單,存放訂閱者的回調函數
//增加訂閱者
salesOffices.listen = function ( key, fn ){
if( !this.clientList[ key] ){
this.clientList[ key ] = []; //如果還沒有訂閱過此類消息,給該類消息建立一個緩存清單
}
this.clientList[ key ].push( fn ); //訂閱的消息存進緩存清單
};
//釋出消息
salesOffices.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);
}
};
salesOffices.listen('squareMeter80', function(price){
console.log('價格=' + price);
});
salesOffices.listen('squareMeter100', function(price){
console.log('價格=' + price);
});
salesOffices.trigger('squareMeter80',20000); //釋出88平方米房子的價格
salesOffices.trigger('squareMeter100',30000); //釋出110平方米房子的價格
接下來實作一個通用的釋出-訂閱模式。
var event = {
clientList : [],//緩存清單,存放訂閱者的回調函數
//增加訂閱者
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(){
var fns = this.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);
}
}
}
}
};
//給所有的對象都動态安裝釋出-訂閱模式
var installEvent = function ( obj ){
for( var i in event){
obj[ i ] = event[ i ];
}
}
測試:
var salesOffices = {};
var fn1,fn2;
installEvent(salesOffices);
//A訂閱消息
salesOffices.listen('squareMeter88', fn1 = function( price ){
console.log('價格=' + price);
});
//B訂閱消息
salesOffices.listen('squareMeter88', fn2 = function( price ){
console.log('價格=' + price);
});
salesOffices.remove('squareMeter88', fn1 );//删除A的訂閱
salesOffices.trigger('squareMeter88', 20000 );//價格=20000