[JS設計模式]:觀察者模式(即發佈-訂閱者模式)(4)

簡介

觀察者模式又叫發佈---訂閱模式,它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知。javascript

舉一個現實生活中的例子,例如小紅在淘寶的一家店裏看上了一雙紅色的鞋,小李也在這家店裏面看上了一頂黑色的帽子,可是聯繫賣家時,賣家回答這兩樣都沒貨了。賣家告訴小紅小李,要是喜歡的話,能夠關注下店鋪,到貨了,我會給你們通知的。這就是一個典型的發佈-訂閱模式,賣家是發佈者,買家是訂閱者。當貨來的時候,會依次通知小紅、小李等買家,依次給他們發消息通知。html

代碼實現

var goodsObj = {};
goodsObj.list = []; //存放訂閱者

//訂閱者
goodsObj.subscribe = function(key,fn){
    if(!this.list[key]){
        this.list[key] = [];
    }
    //訂閱的消息存放到緩存列表中
    this.list[key].push(fn);
}

//取消訂閱
goodsObj.unsubscribe = function(key,fn){
    var fns = this.list[key];
    //若是key對應的消息沒有訂閱過的話,則返回
    if(!fns){ 
        return false;
    }
    //若是沒有傳入具體的回調函數,表示取消key對應消息的全部訂閱
    if(!fn){
        fns && (fns.length = 0);
    }else{
        for(var i = fns.length - 1; i >= 0; i--){
            if(fns[i] === fn){
                //刪除訂閱者的回調函數
                fns.splice(i,1); 
            }
        }
    }
}

//發佈者
goodsObj.publish = function(){
    var key = Array.prototype.shift.call(arguments);
    var fns = this.list[key];
    //若是沒有訂閱過該key的消息,直接返回
    if(!fns || fns.length === 0){
        return false;
    }
    for(var i = 0,fn; fn = fns[i++];){
        fn.apply(this,arguments);
    }
}

//小紅訂閱了鞋
goodsObj.subscribe('shoes',fn1 = function(color){
    console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了');
});

//小李訂閱了帽子
goodsObj.subscribe('cap',fn2 = function(size){
    console.log('訂閱帽子的尺寸:' + size  + '到貨了,能夠拍了');
});

//發佈
goodsObj.publish('shoes','red');
goodsObj.publish('cap',40);

//取消訂閱
goodsObj.unsubscribe('shoes',fn1);

//發佈消息,看是否能收到
goodsObj.publish('shoes','block');

 優化取消訂閱

爲了優化取消訂閱,在訂閱的時候,給每一個訂閱者一個不一樣的標識,代碼以下:java

var goodsObj = {};
goodsObj.list = {}; //存放訂閱者
var subuid = -1; //每一個訂閱者標識不同

//訂閱者
goodsObj.subscribe = function(key,fn){
    if(!this.list[key]){
        this.list[key] = [];
    }
    //訂閱的消息存放到緩存列表中
    var token= (++subuid).toString();
    this.list[key].push({
        token:token,
        fn:fn
    });
    return token;
}

//取消訂閱
goodsObj.unsubscribe = function(token){
    for(var m in goodsObj.list){
        if(goodsObj.list.hasOwnProperty(m)){
            for(var i = 0, j = goodsObj.list[m].length; i < j; i++ ){
                if(goodsObj.list[m][i].token == token){
                    goodsObj.list[m].splice(i,1);
                    break;
                }
            }
        }
    }
}

//發佈者
goodsObj.publish = function(){
    var key = Array.prototype.shift.call(arguments);
    var fns = this.list[key];
    //若是沒有訂閱過該key的消息,直接返回
    if(!fns || fns.length === 0){
        return false;
    }
    for(var i = 0,obj; obj = fns[i++];){
        obj.fn.apply(this,arguments);
    }
}

//小紅訂閱了鞋
var token1 = goodsObj.subscribe('shoes',function(color){
    console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了');
});

//小李訂閱了帽子
goodsObj.subscribe('cap',function(size){
    console.log('訂閱帽子的尺寸:' + size  + '到貨了,能夠拍了');
});

//發佈
goodsObj.publish('shoes','red');
goodsObj.publish('cap',40);

//取消訂閱
goodsObj.unsubscribe(token1);

//發佈消息,看是否能收到
goodsObj.publish('shoes','block');

封裝全局發佈訂閱對象

封裝一個全局的發佈-訂閱對象,代碼以下:設計模式

var Event = (function(){
    var list = {}, //存放訂閱者
        subscribe,
        unsubscribe,
        publish,
        subuid = -1; //每一個訂閱者標識不同
        
        //訂閱者
        subscribe = function(key,fn){
            if(!list[key]){
                list[key] = [];
            }
            //訂閱的消息存放到緩存列表中
            var token= (++subuid).toString();
            list[key].push({
                token:token,
                fn:fn
            });
            return token;
        };
        
        //取消訂閱
        unsubscribe = function(token){
            for(var m in list){
                if(list.hasOwnProperty(m)){
                    for(var i = 0, j = list[m].length; i < j; i++ ){
                        if(list[m][i].token == token){
                            list[m].splice(i,1);
                            break;
                        }
                    }
                }
            }
        };
        
        //發佈者
        publish = function(){
            var key = Array.prototype.shift.call(arguments);
            var fns = list[key];
            //若是沒有訂閱過該key的消息,直接返回
            if(!fns || fns.length === 0){
                return false;
            }
            for(var i = 0,obj; obj = fns[i++];){
                obj.fn.apply(this,arguments);
            }
        };
        
        return {
            publish:publish,
            subscribe:subscribe,
            unsubscribe:unsubscribe
        }        
})();

//小紅訂閱了鞋
var token1 = Event.subscribe('shoes',function(color){
    console.log('訂閱鞋的顏色:' + color + '到貨了,能夠拍了');
});

//小李訂閱了帽子
Event.subscribe('cap',function(size){
    console.log('訂閱帽子的尺寸:' + size  + '到貨了,能夠拍了');
});

//發佈
Event.publish('shoes','red');
Event.publish('cap',40);

//取消訂閱
Event.unsubscribe(token1);

//發佈消息,看是否能收到
Event.publish('shoes','block');

實例

咱們使用上面封裝的全局的發佈-訂閱對象來實現兩個模塊之間的通訊問題;好比如今有一個頁面有一個按鈕,每次點擊此按鈕後,div中會顯示此按鈕被點擊的總次數;以下代碼:緩存

<button id="clickBtn">點擊我計數</button>
<div id="showCount">0</div>

JS代碼以下:app

(function(){
    var count = 0;
    //發佈(責處理點擊操做、發佈消息)
    document.getElementById('clickBtn').addEventListener('click',function(){
        Event.publish('add',count++);
    },false);
    
    //訂閱(負責監聽add這個消息,並把點擊的總次數顯示到頁面上來)
    Event.subscribe('add',function(curCount){
        document.getElementById('showCount').innerHTML = curCount;
    })
})();

訂閱發佈更精簡代碼:函數

var Event = function(){
    var listen,obj,remove,one,trigger,__this;
    obj = {};
    __this = this;
    listen = function(key,eventfn){
        var _ref,stack;
        stack = (_ref = obj[key]) != null ? _ref : obj[key] = [];
        return stack.push(eventfn);
    };
    one = function(key,eventfn){
        remove(key);
        return listen(key,eventfn);
    };
    remove = function(key){
        var _ref;
        return (_ref = obj[key]) != null ? _ref.length = 0 : void 0;
    };
    trigger = function(){
        var fn,stack,_i,_len,_ref,key;
        key = Array.prototype.shift.call(arguments);
        stack = (_ref = obj[key]) != null ? _ref :obj[key] = [];
        for(_i=0,_len=stack.length;_i<_len;_i++){
            fn = stack[_i];
            if(fn.apply(__this,arguments) === false){
                return false;
            }
        }
    }
    
    return {
        listen:listen,
        one:one,
        remove:remove,
        trigger:trigger
    }
}

var addTv = Event();

addTv.listen('play',function(data){
    console.log('今天上午要播放的電影是:' +data.name);
});
addTv.one('play',function(data){
    console.log('只有我訂閱的電影信息:' +data.name);
});
addTv.trigger('play',{'name':'國產凌凌漆'});

addTv.remove('play');
addTv.trigger('play',{'name':'國產凌凌漆22'});

 參考

相關文章
相關標籤/搜索