觀察者模式,也叫訂閱-發佈模式。顧名思義,就是訂閱某些功能,而後在適當的時機發布出來,也就是執行這些功能。
訂閱:就是把幾個函數推入數組中待用;
發佈:就是把緩存在數組中的函數拿出來執行;css
var login = {}; login.eventList = {}; //將函數推入數組中保存,待用 login.listen = function(key, fn) { if(!this.eventList[key]) { this.eventList[key] = []; } this.eventList[key].push(fn); } login.trigger = function(key) { var fns = this.eventList[key]; if(!fns || fns.length === 0) { return false; } for(var i=0; i<fns.length;i++) { fns[i](); } } //訂閱 login.listen('loginSuccess', function() { console.log('顯示用戶頭像'); }) login.listen('loginSuccess', function() { console.log('顯示消息列表'); }) //發佈 login.trigger('loginSuccess');
應用場景:
如今前端領域,SPA單頁應用已經很是廣泛了,每一個頁面,都是用ajax異步請求。ajax請求有一個比較鬧心的問題,就是層級回調。好比:
有一個頁面,須要調用三個數據接口。
第一個是login登陸接口,
第二個是根據登陸接口返回的id,調取頭像接口。
第三個是根據登陸接口返回的id,調取消息列表接口。html
通常狀況下會這麼寫:前端
$.ajax({ url: 'http://ajax.login.com', dataType: 'json', success: function(data) { getAvatar(data.id); getMsg(data.id); ... } })
這樣寫雖然沒有問題,但卻不容易維護。若是哪天改了需求,須要加個接口,你還得翻出這段代碼,找到success回調,再加上一個函數。加函數還算好的,有的人會直接在success回調裏繼續寫$.ajax這樣的代碼,一級一級的這麼摞着寫,這樣代碼很快就會變成一堆大便,變得不可維護。這種寫法叫作造糞模式,百分百的造出垃圾來。由於耦合性太大,接口調用都成了拴在一條繩子上的螞蚱,一扯就是一坨。
如何解耦呢?就是利用訂閱發佈模式,咱們能夠在getAvatar方法中,訂閱(listen)login接口,而一旦login接口走到success回調,咱們就發佈(trigger)一下jquery
var event = { eventList: {}, listen: function(key, fn) { if(!this.eventList[key]) { this.eventList[key] = []; } this.eventList[key].push(fn); }, trigger: function() { var key = Array.prototype.shift.call(arguments); var fns = this.eventList[key]; if(!fns || fns.length === 0) { return false; } for(var i=0; i<fns.length; i++) { fns[i].apply(this, arguments); } } }; var installEvent = function(obj) { //淺拷貝 for(var i in event) { obj[i] = event[i]; } } var login = {}; installEvent(login); //訂閱 login.listen('loginSuccess', function() { console.log('顯示用戶頭像'); }); login.listen('loginSuccess', function() { console.log('顯示消息列表'); }); //發佈 login.trigger('loginSuccess');
如今訂閱沒有問題了,那如何取消訂閱呢?咱們再加上取消訂閱函數ajax
var event = { eventList: {}, listen: function(key, fn) { if(!this.eventList[key]) { this.eventList[key] = []; } this.eventList[key].push(fn); }, remove: function(key, fn) { var fns = this.eventList[key]; if(!fns) { return false; } if(!fn) { //若是沒有回調,表示取消此key下的全部方法 fns && (fns.length); } else { for(var i=0; i<fns.length; i++) { if(fns[i] == fn) { fns.splice(i, 1); } } } }, trigger: function() { var key = Array.prototype.shift.call(arguments); var fns = this.eventList[key]; if(!fns || fns.length === 0) { reutrn false; } for(var i=0; i<fns.length; i++) { fns[i].apply(this, arguments); } } }; var installEvent = function(obj) { for(var i in event) { obj[i] = event[i]; } }; var login = {}; installEvent(login); //顯示用戶頭像 function showAvatar() { console.log('顯示頭像數據'); }; //顯示消息列表 function showMessage() { console.log('顯示消息列表'); }; //訂閱 login.listen('loginSuccess', showAvatar); login.listen('loginSuccess', showMessage); //發佈 login.trigger('loginSuccess'); //取消訂閱 login.remove('loginSuccess', showAvatar); //再次發佈 login.trigger('loginSuccess');
咱們的訂閱發佈模式走到這裏,基本上已經完善了。最後咱們來看一下ajax回調問題怎麼來解決。咱們其實根本不須要在登陸的ajax回調中加拉取頭像等邏輯,而只需讓拉取頭像功能訂閱登陸接口便可,當登陸工做完成後會發布,也就是觸發緩存在數組中的函數執行。json
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>訂閱-觀察者模式</title> <script src="http://mockjs.com/dist/mock.js"></script> <script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script> </head> <body> </body> <script> Mock.mock('http://ajax.login.com', { 'name': '@name', 'age|1-100': 1 }); var event = { eventList: {}, listen: function(key, fn) { if(!this.eventList[key]) { this.eventList[key] = []; } this.eventList[key].push(fn); }, remove: function(key, fn) { var fns = this.eventList[key]; if(!fns) { return false; } if(!fn) { fns && fns.length = 0; } else { for(var i=0; i<fns.length; i++) { if(fn == fns[i]) { fns.splice(i, 1); } } } }, trigger: function() { var key = Array.prototype.shift.call(arguments); var fns = this.eventList[key]; if(!fns || fns.length === 0) { return false; } for(var i=0; i<fns.length; i++) { fns[i].apply(this, arguments); } } }; var installEvent = function(obj) { for(var i in event) { obj[i] = event[i]; } }; var login = {}; installEvent(obj); var avatar = (function() { login.listen('loginSucc', function() { avatar.setAvatar(data); }); return { setAvatar: function(data) { console.log('顯示用戶' + data['name'] + '的頭像'); } } })(); var message = (function() { login.listen('loginSucc', function(data) { message.setMsg(data); }); return { setMsg: function(data) { setTimeout(function() { console.log('顯示用戶' + data['name'] + '的消息'); }) } } })(); //發佈 $.ajax({ url: 'http://ajax.login.com', dataType: 'json', success: function(data) { login.trigger('loginSucc', data); } }) </script> <html>
事實上,還有一種更廣泛意義的訂閱發佈模式。好比在一個按鈕上綁定click事件,這其實就是一個訂閱的過程;而鼠標點擊就是發佈。數組