js設計模式筆記 - 觀察者模式

觀察者模式,定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。
事實上,只要你曾經在DOM節點上綁定過事件函數,那麼你就曾經使用過觀察者模式了!php

document.body.addEventListener('click', function () {
    alert(2);
});

可是這只是對觀察者模式最簡單的使用,在不少場景下咱們常常會實現一些自定義事件來知足咱們的需求。前端

舉個例子:

你去一家公司應聘,談了一頓下來,hr跟你說:"好了,你回去等通知吧!"。
這個時候,1.你會問公司的電話,而後天天打過去問一遍結果
          2.把本身的手機號留給hr,而後等他給你打電話
          
相信不少時候呢,你們都是選擇了後者。
萬一你天天給hr打電話弄煩他了,或許他原本打算招你的,如今也再也不打算再鳥你啦!

那麼這個時候,hr就至關於一個發佈者,而你就是一個訂閱者啦!

好吧,大部分叫你回去等消息的就等於沒救啦......
我還遇到過一個若是你沒被錄取,就連通知都不通知你的公司!

那麼一個簡單的觀察者模式應該怎麼實現呢?web

  1. 要指定一個發佈者;
  2. 給發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者;(這家公司不少人來應聘)
  3. 最後發佈消息的時候,發佈者會遍歷這個緩存列表,依次觸發裏面存放的訂閱者回調函數;(你up or 你over)
var event = {};    //發佈者(hr)
event.clietList = []; //發佈者的緩存列表(應聘者列表)

event.listen = function(fn) { //增長訂閱者函數
    this.clietList.push(fn);
};

event.trigger = function() { //發佈消息函數
    for (var i = 0; i < this.clietList.length; i++) {
        var fn = this.clietList[i];
        fn.apply(this, arguments);
    }
};

event.listen(function(time) { //某人訂閱了這個消息
    console.log('正式上班時間:' + time);
});

event.trigger('2016/10',yes); //發佈消息
//輸出 正式上班時間:2016/10

到這裏,咱們已經實現了一個最簡單的觀察者模式了!緩存

可是上面的函數其實存在一個問題,那就是發佈者沒辦法選擇本身要發佈的消息類型!
好比這家公司同時在招php,web前端,若是使用上面的函數就沒辦法區分職位了!只能一次性把所有訂閱者都發送一遍消息。
對上面的代碼進行改寫:閉包

var event = {}; //發佈者(hr)
event.clietList = []; //發佈者的緩存列表(應聘者列表)

event.listen = function(key, fn) { //增長訂閱者函數
    if (!this.clietList[key]) {
        this.clietList[key] = [];
    }
    this.clietList[key].push(fn);
};

event.trigger = function() { //發佈消息函數
    var key = Array.prototype.shift.call(arguments),
        fns = this.clietList[key];
    for (var i = 0; i < fns.length; i++) {
        var fn = fns[i];
        fn.apply(this, arguments);
    }
};

event.listen('web前端', fn1 = function(time) { //小強訂閱了這個消息。
    console.log('姓名:小強');
    console.log('正式上班時間:' + time);
});

event.listen('web前端', fn2 = function(time) { //大大強訂閱了這個消息
    console.log('姓名:大大強');
    console.log('正式上班時間:' + time);
});

//發佈者發佈消息
event.trigger('web前端','小強', '2016/10'); //姓名:小強   正式上班時間:2016/10  
event.trigger('php','大大強', '2016/15'); //姓名:大大強   正式上班時間:2016/15

經過添加了一個key,咱們實現了對職位的判斷。app

有了訂閱事件,咱們怎麼能少了取消訂閱事件呢?函數

event.remove = function(key, fn) {
    var fns = this.clietList[key];
    if (!fns) {
        return false;
    }
    if (!fn) { //若是沒有傳入fn回調函數,直接取消key對應消息的全部訂閱
        this.clietList[key] = [];
    } else {
        for (var i = 0; i < fns.length; i++) { //遍歷回調函數列表
            var _fn = fns[i];
            if (_fn === fn) {
                fns.splice(i, 1); //刪除訂閱者的回調函數
            }
        }
    }
};
//這時候必須指定回調函數,不然沒法在remove函數中進行對比刪除。
event.listen('web前端', fn1 = function(time) { //小強訂閱了這個消息。
    console.log('姓名:小強');
    console.log('正式上班時間:' + time);
});

event.listen('web前端', fn2 = function(time) { //大大強訂閱了這個消息
    console.log('姓名:大大強');
    console.log('正式上班時間:' + time);
});

event.remove('web前端',fn1);

//發佈者發佈消息
event.trigger('web前端','2016/10');

//輸出 姓名:大大強   正式上班時間:2016/10

對上面代碼進行改進,建立一個全局對象來實現觀察者模式,
使用閉包實現私有變量,僅暴露必須的API給使用者:this

var event = (function() {
    var clietList = []; //發佈者的緩存列表(應聘者列表)

    var listen = function(key, fn) { //增長訂閱者函數
        if (!this.clietList[key]) {
            this.clietList[key] = [];
        }
        this.clietList[key].push(fn);
    };

    var trigger = function() { //發佈消息函數
        var key = Array.prototype.shift.call(arguments),
            fns = this.clietList[key];
        for (var i = 0; i < fns.length; i++) {
            var fn = fns[i];
            fn.apply(this, arguments);
        }
    };

    var remove = function(key, fn) {
        var fns = this.clietList[key];
        if (!fns) {
            return false;
        }
        if (!fn) { //若是沒有傳入fn回調函數,直接取消key對應消息的全部訂閱
            this.clietList[key] = [];
        } else {
            for (var i = 0; i < fns.length; i++) { //遍歷回調函數列表
                var _fn = fns[i];
                if (_fn === fn) {
                    fns.splice(i, 1); //刪除訂閱者的回調函數
                }
            }
        }
    };
    
    return{
        listen:listen,
        trigger:trigger,
        remove:remove
    }
})();

觀察者模式進階:prototype

  1. 使用命名空間防止事件名衝突
  2. 實現先發布後訂閱功能
相關文章
相關標籤/搜索