觀察者模式
定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知
前一段時間一直在寫CSS3的文章
一直都沒寫設(shè)計模式
今天來寫寫大名鼎鼎觀察者模式
先畫張圖

觀察者模式的理解
我覺得還是發(fā)布-訂閱模式的叫法更容易我們理解
(不過也有的書上認為它們是兩種模式……)
這就類似我們在微信平臺訂閱了公眾號
當它有新的文章發(fā)表后,就會推送給我們所有訂閱的人
我們可以看到例子中這種模式的優(yōu)點
我們作為訂閱者不必每次都去查看這個公眾號有沒有新文章發(fā)布,
公眾號作為發(fā)布者會在合適時間通知我們
我們與公眾號之間不再強耦合在一起。公眾號不關(guān)心誰訂閱了它,
不管你是男是女還是寵物狗,它只需要定時向所有訂閱者發(fā)布消息即可
很簡單的道理,過年的時候,群發(fā)祝福短信一定要比挨個發(fā)短信方便的多
通過上面的例子映射出我們觀察者模式的優(yōu)點
可以廣泛應(yīng)用于異步編程,它可以代替我們傳統(tǒng)的回調(diào)函數(shù)
我們不需要關(guān)注對象在異步執(zhí)行階段的內(nèi)部狀態(tài),我們只關(guān)心事件完成的時間點
取代對象之間硬編碼通知機制,一個對象不必顯式調(diào)用另一個對象的接口,而是松耦合的聯(lián)系在一起
雖然不知道彼此的細節(jié),但不影響相互通信。更重要的是,其中一個對象改變不會影響另一個對象
可能看完這些一臉懵逼,不過沒關(guān)系
下面我們會深入體會它的優(yōu)點
自定義事件
其實觀察者模式我們都曾使用過,就是我們熟悉的事件
但是內(nèi)置的事件很多時候不能滿足我們的要求
所以我們需要自定義事件
現(xiàn)在我們想實現(xiàn)這樣的功能
定義一個事件對象,它有以下功能
監(jiān)聽事件(訂閱公眾號)
觸發(fā)事件(公眾號發(fā)布)
移除事件(取訂公眾號)
當然我們不可能只訂閱一個公眾號,可能會有很多
所以我們要針對不同的事件設(shè)置不同的”鍵”
這樣我們儲存事件的結(jié)構(gòu)應(yīng)該是這樣的
//偽代碼
Event = {
? ? name1: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],
? ? name2: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],
? ? name3: [回調(diào)函數(shù)1,回調(diào)函數(shù)2,...],
}
代碼如下
var Event = (function(){
? ? var list = {},
? ? ? ? listen,
? ? ? ? trigger,
? ? ? ? remove;
? ? listen = function(key,fn){ //監(jiān)聽事件函數(shù)
? ? ? ? if(!list[key]){
? ? ? ? ? ? list[key] = []; //如果事件列表中還沒有key值命名空間,創(chuàng)建
? ? ? ? }
? ? ? ? list[key].push(fn); //將回調(diào)函數(shù)推入對象的“鍵”對應(yīng)的“值”回調(diào)數(shù)組
? ? };
? ? trigger = function(){ //觸發(fā)事件函數(shù)
? ? ? ? var key = Array.prototype.shift.call(arguments); //第一個參數(shù)指定“鍵”
? ? ? ? msg = list[key];
? ? ? ? if(!msg || msg.length === 0){
? ? ? ? ? ? return false; //如果回調(diào)數(shù)組不存在或為空則返回false
? ? ? ? }
? ? ? ? for(var i = 0; i < msg.length; i++){
? ? ? ? ? ? msg[i].apply(this, arguments); //循環(huán)回調(diào)數(shù)組執(zhí)行回調(diào)函數(shù)
? ? ? ? }
? ? };
? ? remove = function(key, fn){ //移除事件函數(shù)
? ? ? ? var msg = list[key];
? ? ? ? if(!msg){
? ? ? ? ? ? return false; //事件不存在直接返回false
? ? ? ? }
? ? ? ? if(!fn){
? ? ? ? ? ? delete list[key]; //如果沒有后續(xù)參數(shù),則刪除整個回調(diào)數(shù)組
? ? ? ? }else{
? ? ? ? ? ? for(var i = 0; i < msg.length; i++){
? ? ? ? ? ? ? ? if(fn === msg[i]){
? ? ? ? ? ? ? ? ? ? msg.splice(i, 1); //刪除特定回調(diào)數(shù)組中的回調(diào)函數(shù)
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? };
? ? return {
? ? ? ? listen: listen,
? ? ? ? trigger: trigger,
? ? ? ? remove: remove
? ? }
})();
var fn = function(data){
? ? console.log(data + '的推送消息:xxxxxx......');
}
Event.listen('某公眾號', fn);
Event.trigger('某公眾號', '2016.11.26');
Event.remove('某公眾號', fn);
通過這種全局的Event對象,我們可以利用它在兩個模塊間實現(xiàn)通信
并且兩個模塊互不干擾
//偽代碼
module1 = function(){
? ? Event.listen(...);
}
module2 = function(){
? ? Event.trigger(...);
}
完整的觀察者對象
我們上面寫的Event對象還是存在一些問題
只能先“訂閱”再“發(fā)布”
全局對象會產(chǎn)生命名沖突
對于第一點,在我們訂閱了一個公眾號之前,它是同樣會定時推送消息的
我們訂閱后,同樣可以查看歷史推送
所以我們應(yīng)該是實現(xiàn)先發(fā)布再訂閱仍然可以查看歷史消息
對于第二點,如果我們每個人都使用上面我們定義的函數(shù)
所有事件都放在一個list當中
時間長了一定會產(chǎn)生命名沖突
最好的辦法就是為對象增加創(chuàng)建命名空間的功能
現(xiàn)在我們來豐滿我們的事件對象
(這里我就盜用大神寫的代碼了,不同的人不同庫實現(xiàn)的方式都不同)
這個完整版稍微了解一下就好了
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(){
? ? ? ? ? ? ? ? ? ? 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;
})();
/********* 先發(fā)布后訂閱 *********/
Event.trigger('click',1);
Event.listen('click',function(a){
? ? console.log(a);? //1
});
/********* 使用命名空間 *********/
Event.create('namespace1').listen('click',function(a){
? ? console.log(a);? //1
})
Event.create('namespace1').trigger('click',1);
Event.create('namespace3').listen('click',function(a){
? ? console.log(a);? //2
})
Event.create('namespace3').trigger('click',2);
總結(jié)
觀察者模式有兩個明顯的優(yōu)點
時間上解耦
對象間解耦
它應(yīng)用廣泛,但是也有缺點
創(chuàng)建這個函數(shù)同樣需要內(nèi)存,過度使用會導致難以跟蹤維護
---------------------
作者:_accelerator_
來源:CSDN
原文:https://blog.csdn.net/q1056843325/article/details/53353850
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!