《JavaScript設計模式與開發實踐》模式篇(5)—— 觀察者模式

嗨呀豆豆呢發表於2018-12-13

釋出—訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀 態發生改變時,所有依賴於它的物件都將得到通知。在 JavaScript 開發中,我們一般用事件模型 來替代傳統的釋出—訂閱模式。

故事背景

小明最近看上了一套房子,到了售樓處之後才被告知,該樓盤的房子早已售罄。好在售樓 MM 告訴小明,不久後還有一些尾盤推出,開發商正在辦理相關手續,手續辦好後便可以購買。 但到底是什麼時候,目前還沒有人能夠知道。於是小明記下了售樓處的電話,以後每天都會打電話過去詢問是不是已經到了購買時間。除 了小明,還有小紅、小強、小龍也會每天向售樓處諮詢這個問題。一個星期過後,售樓 MM 決 定辭職,因為厭倦了每天回答 1000 個相同內容的電話。當然現實中沒有這麼笨的銷售公司,實際上故事是這樣的:小明離開之前,把電話號碼留在 了售樓處。售樓 MM 答應他,新樓盤一推出就馬上發資訊通知小明。小紅、小強和小龍也是一 樣,他們的電話號碼都被記在售樓處的花名冊上,新樓盤推出的時候,售樓 MM 會翻開花名冊,遍歷上面的電話號碼,依次傳送一條簡訊來通知他們。

傳送簡訊通知就是一個典型的釋出—訂閱模式,小明、小紅等購買者都是 訂閱者,他們訂閱了房子開售的訊息。售樓處作為釋出者,會在合適的時候遍歷花名冊上的電話號碼,依次給購房者釋出訊息。

程式碼實現

  • 先訂閱後釋出模式
var DEvent = (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 (let index = 0;
index <
fns.length;
index++) {
const fn = fns[index];
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 = fn.length - 1;
l >
0 ;
l--) {
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1);

}
}
}
};
return {
listen, trigger, remove
};

})();
Event.listen( 'squareMeter88', function( price ){
// 小紅訂閱訊息 console.log( '價格= ' + price );
// 輸出:'價格=2000000'
});
Event.trigger( 'squareMeter88', 2000000 );
// 售樓處釋出訊息複製程式碼

應用場景

  • 網站登入假如我們正在開發一個商城網站,網站裡有 header 頭部、nav 導航、訊息列表、購物車等模組。這幾個模組的渲染有一個共同的前提條件,就是必須先用 ajax 非同步請求獲取使用者的登入資訊。 這是很正常的,比如使用者的名字和頭像要顯示在 header 模組裡,而這兩個欄位都來自使用者登入後 返回的資訊。至於 ajax 請求什麼時候能成功返回使用者資訊,這點我們沒有辦法確定。現在的情節看起來像 極了售樓處的例子,小明不知道什麼時候開發商的售樓手續能夠成功辦下來。
$.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(){
login.listen( 'loginSucc', function( data ){// nav 模組 nav.setAvatar( data.avatar );

});
return {
setAvatar: function( avatar ){
console.log( '設定 nav 模組的頭像' );

}
}
})();
複製程式碼
  • 先發布後訂閱模式(提供建立名稱空間的功能)
var Event = (function(){ 
var global = this, Event, _default = 'default';
Event = function(){
var _listen, _trigger, _remove, _slice = Array.prototype.slice, _shift = Array.prototype.shift, _unshift = Array.prototype.unshift, namespaceCache = {
}, _create, find, each = function( ary, fn ){
var ret;
for ( var i = 0, l = ary.length;
i <
l;
i++ ){
var n = ary[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;
i >
= 0;
i-- ){
if( cache[ key] [i] === fn) {
cache[key].splice(i, 1);

}
}
} else{
cache[ key ] = [];

}
}
};
_trigger = function(){
var cache = _shift.call(arguments), key = _shift.call(arguments), args = arguments, _self = this, ret, stack = cache[ key ];
if ( !stack || !stack.length ) {
return;

} return each( stack, function(){
return this.apply( _self, args );

});

};
_create = function( namespace ){
var namespace = namespace || _default;
var cache = {
}, offlineStack = [],// 離線事件 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, _self = this;
_unshift.call( arguments, cache );
args = arguments;
fn = function(){
return _trigger.apply( _self, 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;

})();
複製程式碼

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

來源:https://juejin.im/post/5c1261526fb9a049b221c1d1

相關文章