訂閱發佈模式若是按數學翻譯其實就是.一對多的映射關係.怎麼解釋呢? 就是一個開關,同時並聯幾個燈泡(在不一樣房間),觸發的時候,幾個燈泡都會獲得指令,而後執行發光的行爲。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的查找也會越複雜。因此,發佈訂閱模式使用的時候,但願你們好好想想,不要爲了模式而去模式。