有人發佈信息有人“訂閱”信息並進行處理。
DOM事件
document.body.addEventListener( 'click', function(){
alert(2);
}, false );
document.body.click(); // 模擬用戶點擊
售樓處level01
售樓處發佈樓盤信息,訂閱者訂閱(即把你需要的行爲存入售樓處)這些信息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
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 ); // (2) // arguments 是發佈消息時帶上的參數
}
};
// 下面我們來進行一些簡單的測試:
salesOffices.listen( function( price, squareMeter ){ // 小明訂閱消息
console.log( '價格= ' + price );
console.log( 'squareMeter= ' + squareMeter );
});
salesOffices.trigger( 2000000, 88 ); // 輸出:200 萬,88 平方米
salesOffices.trigger( 3000000, 110 ); // 輸出:300 萬,110 平方米
</script>
</body>
</html>
缺點:沒有訂閱的也會發來。
售樓處level02
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
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 ); // (2) // arguments 是發佈消息時附送的參數
}
};
salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱 88 平方米房子的消息
console.log( '價格= ' + price ); // 輸出: 2000000
});
salesOffices.listen( 'squareMeter110', function( price ){ // 小紅訂閱 110 平方米房子的消息
console.log( '價格= ' + price ); // 輸出: 3000000
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 發佈 88 平方米房子的價格
salesOffices.trigger( 'squareMeter110', 3000000 ); // 發佈 110 平方米房子的價格
</script>
</body>
</html>
解決了level01的問題
接受者只要有相同key的的接受者都可以接受到信息。
網站登錄level01
login.succ(function(data){
header.setAvatar( data.avatar); // 設置 header 模塊的頭像
nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
message.refresh(); // 刷新消息列表
cart.refresh(); // 刷新購物車列表
});
等到有一天,項目中又新增了一個收貨地址管理的模塊,這個模塊本來是另一個同事所寫的,而此時你正在馬來西亞度假,但是他卻不得不給你打電話:“Hi,登錄之後麻煩刷新一下收貨地址列表。”於是你又翻開你 3個月前寫的登錄模塊,在最後部分加上這行代碼:
login.succ(function(data){
header.setAvatar( data.avatar); // 設置 header 模塊的頭像
nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
message.refresh(); // 刷新消息列表
cart.refresh(); // 刷新購物車列表
address.refresh(); // 增加這行代碼
});
網站登錄level02
用發佈 — 訂閱模式重寫之後,對用戶信息感興趣的業務模塊將自行訂閱登錄成功的消息事件。當登錄成功時,登錄模塊只需要發佈登錄成功的消息,而業務方接受到消息之後,就會開始進行
各自的業務處理,登錄模塊並不關心業務方究竟要做什麼,也不想去了解它們的內部細節。改善後的代碼如下:
<script>
/* login.succ(function(data){
header.setAvatar( data.avatar); // 設置 header 模塊的頭像
nav.setAvatar( data.avatar ); // 設置導航模塊的頭像
message.refresh(); // 刷新消息列表
cart.refresh(); // 刷新購物車列表
});*/
$.ajax( 'http:// xxx.com?login', function(data){ // 登錄成功
login.trigger( 'loginSucc', data); // 發佈登錄成功的消息
});
var header = (function(){ // header 模塊
login.listen( 'loginSucc', function( data){
header.setAvatar( data.avatar );
});
return {
setAvatar: function( data ){
console.log( '設置 header 模塊的頭像' );
}
}
})();
var nav = (function(){ // nav 模塊
login.listen( 'loginSucc', function( data ){
nav.setAvatar( data.avatar );
});
return {
setAvatar: function( avatar ){
console.log( '設置 nav 模塊的頭像' );
}
}
})();
/* 如果有一天在登錄完成之
後,又增加一個刷新收貨地址列表的行爲,那麼只要在收貨地址模塊里加上監聽消息的方法即可,
而這可以讓開發該模塊的同事自己完成,你作爲登錄模塊的開發者,永遠不用再關心這些行爲了。
代碼如下:*/
var address = (function(){ // nav 模塊
login.listen( 'loginSucc', function( obj ){
address.refresh( obj );
});
return {
refresh: function( avatar ){
console.log( '刷新收貨地址列表' );
}
}
})();
售樓處 level03:全局的發佈 - 訂閱對象
發佈 — 訂閱模式可以用一個全局的 Event 對象來實現,訂閱者不需要了解消息來自哪個發佈者,發佈者也不知道消息會推送給哪些訂閱者, Event 作爲一個類似“中介者”的角色,把訂閱者和發佈者聯繫起來。見如下代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var Event = (function(){
var clientList = {},
listen,
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: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price ); // 輸出:'價格=2000000'
});
Event.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price ); // 輸出:'價格=2000000'
});
Event.listen( 'squareMeter88', fn=function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price ); // 輸出:'價格=2000000'
});
Event.remove( 'squareMeter88');
Event.trigger( 'squareMeter88', 2000000 ); // 售樓處發佈消息
</script>
</body>
</html>
網站登錄level04
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="PublishSubscribe.js"></script>
</head>
<body>
<script>
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 ){ // 如果沒有綁定對應的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是 trigger 時帶上的參數
}
},
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 );
}
}
}
}
};
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱消息
console.log( '價格= ' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price );
});
/*salesOffices.listen( 'squareMeter88', function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price ); // 輸出:'價格=2000000'
});
salesOffices.listen( 'squareMeter88', fn=function( price ){ // 小紅訂閱消息
console.log( '價格= ' + price ); // 輸出:'價格=2000000'
});
Event.remove( 'squareMeter88');*/
salesOffices.trigger( 'squareMeter88', 2000000 ); // 輸出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 輸出:3000000
</script>
</body>
</html>
模塊間通信
上一節中實現的發佈 — 訂閱模式的實現,是基於一個全局的 Event 對象,我們利用它可以在兩個封裝良好的模塊中進行通信,這兩個模塊可以完全不知道。
比如現在有兩個模塊,a模塊裏面有一個按鈕,每次點擊按鈕之後,b模塊裏的 div中會顯示按鈕的總點擊次數,我們用全局發佈 — 訂閱模式完成下面的代碼,使得 a 模塊和 b 模塊可以在保
持封裝性的前提下進行通信。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="PublishSubscribe.js"></script>
</head>
<body>
<button id="count">點我</button>
<div id="show"></div>
<script>
var a = (function(){
var count = 0;
var button = document.getElementById( 'count' );
button.onclick = function(){
Event.trigger( 'add', count++ );
}
})();
var b = (function(){
var div = document.getElementById( 'show' );
Event.listen( 'add', function( count ){
div.innerHTML = count;
});
})();
</script>
</body>
</html>
即發佈和訂閱在不同的兩個模塊裏。