js中的一對多 - 訂閱發佈模式

訂閱發佈模式若是按數學翻譯其實就是.一對多的映射關係.怎麼解釋呢? 就是一個開關,同時並聯幾個燈泡(在不一樣房間),觸發的時候,幾個燈泡都會獲得指令,而後執行發光的行爲。jquery

訂閱發佈模式

這種模式在js裏面有這自然的優點,由於js自己就是事件驅動型語言。好比,頁面上有一個button, 你點擊一下就會觸發上面的click事件,而此時有一部分程序正在監聽這個事件,隨之觸發相關的處理程序.chrome

var button = $("#button");
button.on("click",function(){
    console.log("I am pressing the button");
});

事實上,咱們也早就熟悉這個模式了,只是不知道這叫什麼(訂閱發佈模式 又名 觀察者模式).
這個模式最大的一個好處就在於,可以解耦回調函數,讓你的程序看起來更美觀(雖然如今有Promise和Deferred幫忙,可是不完全)。瀏覽器

訂閱發佈模式內涵

說了點理論,來些乾貨。(如下將訂閱發佈模式簡稱爲觀察者模式).
觀察者模式無非就兩個部分,一個訂閱(監聽程序),一個發佈(觸發事件).而他們中間的連接樞紐就是事件。 一般來講,咱們能夠自定義一個觀察者模式--使用自定義事件.
(因爲IE過於SB,我就不想說他的事了,下面這些適用於chrome, iE9+,ff等其餘現代瀏覽器中)app

// Create the event.  //建立一個事件
var event = document.createEvent('Event'); //Event是自定義的事件名

// Define that the event name is 'build'.
event.initEvent('build', true, true);  //這裏是初始化事件,  //就是些參數而已.

// Listen for the event.
elem.addEventListener('build', function (e) {    //給事件添加監聽
  // e.target matches elem
}, false);

// target can be any Element or other EventTarget.
elem.dispatchEvent(event);    //觸發事件

大體就是這幾個步驟,因爲這樣寫,太非人道了。因此這裏映入jquery的trigger觸發方式(大哥就是大哥~)
在jquery的事件處理中,幾個基本和事件相關的API須要熟悉。一個是on,一個是trigger.異步

$(".ele").on("click",function(){
    console.log("clicking");
});
$(".ele").trigger("click");

這是一個基本的使用,使用trigger來觸發事件.
可是誰尼瑪無聊到連click本身手動觸發啊,這個例子只是講解。如今說一下精華-自定義事件.
在jquery裏面,能夠直接使用on來進行自定義事件的模擬。函數

ele.on("stimulate",function(){  //訂閱一個事件
    ...do sth
});
ele.trigger("stimulate");  //發佈一個事件

這裏trigger只是起到一個開關的做用,那麼我想要他變爲一個管道能夠嗎?
absolutely!!!
在trigger裏面還有第二個參數能夠選擇,即[data]ui

$(document.body).on("stimulate",function(event,name1,name2){  //和節點有關的事件裏,第一個參數永遠是event
    console.log(name1,name2);  //"jimmy","sam"
});
$(document.body).trigger("stimulate",["jimmy","sam"]);

並且若是你自定義事件過多,起名也是件死人的事。因此牛逼的jq會幫你把命名空間處理好.this

ele.on("stimulate.jimmy.sam",function(){  //使用"."連接
    ...do sth
});

他的做用域就是sam>jimmy>stimulate這樣一個關係.
詳情能夠參考: Aron大神些的jquery事件解析。這裏我直接把trigger的源碼貼出來吧.以供參考.
trigger源碼
其實上面的自定義事件的用法也很是有限,由於若是使用一個節點做爲載體的話,這樣的成本也太大了。因此通常在業內已經有成熟的自定義事件的插件了.
不過爲了深刻理解觀察者模式,咱們一步一步來.(爲了裝逼)spa

訂閱發佈模式進階

通過上面的唐僧咒,你們也應該差很少熟悉這個模式的一些關鍵部分。訂閱,發佈,事件。
好,咱們就這3個部分來本身模擬一個。.net

//摘自alloyTeam團隊的曾探·著
var imitate = (function() {
    var imitate = {
        clientList: [],
        listen: function(key, fn) {
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }
            this.clientList[key].push(fn);
        },
        trigger: function() {
            var key = [].shift.call(arguments);
            var fns = this.clientList[key];

            // 若是沒有對應的綁定消息
            if (!fns || fns.length === 0) {
                return false;
            }

            for (var i = 0, fn; fn = fns[i++];) {
                // arguments 是 trigger帶上的參數
                fn.apply(this, arguments);
            }
        }
    }
    return function() {
       return Object.create(imitate);
    }
})();
var eventModel = imitate();  //獲得上面的對象
eventModel.listen("jimmy",function(){console.log("jimmy");});  //jimmy
eventModel.trigger("jimmy");

恩,這樣下來皆能夠和重重的節點說拜拜了。直接使用imitate就能夠進行事件的模擬,並且超快.
固然,這樣寫改進的空間仍是挺大的。解決命名空間問題(暫無論),刪除訂閱問題(這個用處不大)...目前咱們先着手解決這個問題.

var Event = (function() {
    var clientList = {};
    var listen,
        trigger,
        remove;
    listen = function(key, fn) {
        if (!clientList[key]) {
            clientList[key] = [];
        }
        clientList[key].push(fn);
    };

    trigger = function() {
        var key = [].shift.call(arguments);
        var 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];

        // key對應的消息麼有被人訂閱
        if (!fns) {
            return false;
        }

        // 沒有傳入fn(具體的回調函數), 表示取消key對應的全部訂閱
        if (!fn) {
            fns && (fns.length = 0);
        }
        else {
            // 反向遍歷
            for (var i = fns.length - 1; i >= 0; i--) {
                var _fn = fns[i];
                if (_fn === fn) {
                    // 刪除訂閱回調函數
                    fns.splice(i, 1);
                }
            }
        }
    };

    return {
        listen: listen,
        trigger: trigger,
        remove: remove
    }
}());

這個Event對象,可以解決大部分事件模擬的問題。說了這麼多,md,實例嘞。。。等等。立刻來

發佈訂閱模式的實戰

若是你們寫過登陸框(異步登陸哈),應該知道.登陸框和header的部分是徹底不一樣的兩個部分。這個場景就很適合發佈訂閱模式了。
看一下。若是沒有發佈訂閱模式的代碼:

login.on("click",function(){
    var name = $(".username").val().trim;
    http.login(name)  //使用異步Deferred書寫
    .then(function(data){  //如下填寫亂七八糟的處理
        changeName();
        changeAvtar();
        changeStatus();
        ...
    })
});

使用發佈訂閱模式

login.on("click",function(){
    var name = $(".username").val().trim;
    http.login(name)  //使用異步Deferred書寫
    .then(function(data){
        Event.trigger("login",data);  //發佈我登陸成功的狀態,並傳入參數
    })
});
var header = (function() {
    Event.listen("login", function(data) {
        header.changeAvator(data);
    })
    return {
        changeAvator: function(data) {
            ...換頭像
        }
    }
})();
var bar = (function() {
    Event.listen("login", function(data) {
        bar.changeName(data);
    })
    return {
        changeName: function(data) {
            ...換名字
        }
    }
})();

能夠清楚的看到,若是你的登陸狀態改變了的話,會有一系列的訂閱程序發生.並且每一個訂閱之間互不干擾,你能夠隨便添加或者刪除訂閱,這都不會影響你的登陸的執行邏輯. 固然發佈訂閱的使用確定不會僅僅侷限於,登陸狀態的改變。還能夠應用於,模塊間信息的傳遞,分頁頁面的渲染等。可是使用的時候,必定要慎重,由於你訂閱的越多,bug的查找也會越複雜。因此,發佈訂閱模式使用的時候,但願你們好好想想,不要爲了模式而去模式。

相關文章
相關標籤/搜索