观察者模式,也叫订阅-发布模式。顾名思义,就是订阅某些功能,而后在适当的时机发布出来,也就是执行这些功能。
订阅:就是把几个函数推入数组中待用;
发布:就是把缓存在数组中的函数拿出来执行;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事件,这其实就是一个订阅的过程;而鼠标点击就是发布。数组