- 定義
發(fā)布—訂閱模式又叫觀察者模式雁比,它定義對象間的一對多的依賴關(guān)系稚虎,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知偎捎。 - 步驟
下面是實現(xiàn)發(fā)布—訂閱模式的步驟:
1蠢终、先要指定好誰充當發(fā)布者(比如售樓處)
2、然后給發(fā)布者添加一個緩存列表茴她,用于存放回調(diào)函數(shù)以便通知訂閱者(售樓處的花名冊)
3寻拂、最后發(fā)布消息的時候,發(fā)布者會遍歷這個緩存列表败京,依次觸發(fā)里面存放的訂閱者回調(diào)函數(shù)(遍歷花名冊兜喻,挨個發(fā)短信)
另外,還可以往回調(diào)函數(shù)里填入一些參數(shù)赡麦,訂閱者可以接收這些參數(shù)朴皆。這是很有必要的帕识,比如售樓處可以在發(fā)給訂閱者的短信里加上房子的單價、面積遂铡、容積率等信息肮疗,訂閱者接收到這些信息之后可以進行各自的處理
var salesOffices = {}; // 定義售樓處
salesOffices.clientList = []; // 緩存列表,存放訂閱者的回調(diào)函數(shù)
salesOffices.listen = function( fn ){ // 增加訂閱者
this.clientList.push( fn ); // 訂閱的消息添加進緩存列表
};
salesOffices.trigger = function(){ // 發(fā)布消息
for( var i = 0, fn; fn = this.clientList[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是發(fā)布消息時帶上的參數(shù)
}
};
salesOffices.listen( function( price, squareMeter ){ // 小明訂閱消息
console.log( '價格= ' + price );
console.log( 'squareMeter= ' + squareMeter );
});
salesOffices.listen( function( price, squareMeter ){ // 小紅訂閱消息
console.log( '價格= ' + price );
console.log( 'squareMeter= ' + squareMeter );
});
salesOffices.trigger( 2000000, 88 ); // 輸出:200 萬扒接,88 平方米
salesOffices.trigger( 3000000, 110 ); // 輸出:300 萬伪货,110 平方米
但這里還存在一些問題〖卣看到訂閱者接收到了發(fā)布者發(fā)布的每個消息碱呼,雖然小明只想買88平方米的房子,但是發(fā)布者把110平方米的信息也推送給了小明宗侦,這對小明來說是不必要的困擾愚臀。所以有必要增加一個標示key,讓訂閱者只訂閱自己感興趣的消息矾利。改寫后的代碼如下:
var salesOffices = {}; // 定義售樓處
salesOffices.clientList = []; // 緩存列表姑裂,存放訂閱者的回調(diào)函數(shù)
salesOffices.listen = function( key, fn ){
if ( !this.clientList[ key ] ){ // 如果還沒有訂閱過此類消息,給該類消息創(chuàng)建一個緩存列表
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn ); // 訂閱的消息添加進消息緩存列表
};
salesOffices.trigger = function(){ // 發(fā)布消息
var key = Array.prototype.shift.call( arguments ), // 取出消息類型
fns = this.clientList[ key ]; // 取出該消息對應(yīng)的回調(diào)函數(shù)集合
if ( !fns || fns.length === 0 ){ // 如果沒有訂閱該消息男旗,則返回
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是發(fā)布消息時附送的參數(shù)
}
};
salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱88 平方米房子的消息
console.log( '價格= ' + price ); // 輸出: 2000000
});
salesOffices.listen( 'squareMeter110', function( price ){ // 小紅訂閱110 平方米房子的消息
console.log( '價格= ' + price ); // 輸出: 3000000
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 發(fā)布88 平方米房子的價格
salesOffices.trigger( 'squareMeter110', 3000000 ); // 發(fā)布110 平方米房子的價格
- 通用實現(xiàn)
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 ), // (1);
fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 如果沒有綁定對應(yīng)的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是trigger 時帶上的參數(shù)
}
}
};
再定義一個installEvent函數(shù)舶斧,這個函數(shù)可以給所有的對象都動態(tài)安裝發(fā)布—訂閱功能:
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
下面給售樓處對象salesOffices動態(tài)增加發(fā)布—訂閱功能
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱消息
console.log( '價格= ' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price );
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 輸出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 輸出:3000000
- 取消訂閱事件
有時候,也許需要取消訂閱事件的功能察皇。比如小明突然不想買房子了茴厉,為了避免繼續(xù)接收到售樓處推送過來的短信,小明需要取消之前訂閱的事件∪猛現(xiàn)在給event對象增加remove方法
event.remove = function( key, fn ){
var fns = this.clientList[ key ];
if ( !fns ){ // 如果key 對應(yīng)的消息沒有被人訂閱呀忧,則直接返回
return false;
}
if ( !fn ){ // 如果沒有傳入具體的回調(diào)函數(shù)师痕,表示需要取消key 對應(yīng)消息的所有訂閱
fns && ( fns.length = 0 );
}else{
for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍歷訂閱的回調(diào)函數(shù)列表
var _fn = fns[ l ];
if ( _fn === fn ){
fns.splice( l, 1 ); // 刪除訂閱者的回調(diào)函數(shù)
}
}
}
};
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( '價格= ' + price );
});
salesOffices.listen( 'squareMeter88', fn2 = function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price );
});
salesOffices.remove( 'squareMeter88', fn1 ); // 刪除小明的訂閱
salesOffices.trigger( 'squareMeter88', 2000000 ); // 輸出:2000000