发布-订阅模式又叫观察者模式,它定义与对象间的一种一对多的关系,当一个对象的状态发生改变时,全部依赖与它的对象都将获得通知。
发布-订阅模式能够普遍应用与异步编程中,这是一种替代传统回调函数的方案。ajax
发布-订阅能够取代对象之间硬编码的通讯机制,一个对象再也不显式地调用另一个对象的某个接口。编程
var event = { //缓存列表,存放订阅者的回调函数 clientList: [], //增长订阅者, key:事件名,fn:回调函数 listen: function(key, fn){ if(!this.clientList[key]){ this.clientList[key] = [] } this.clientList[key].push(fn) }, //发布消息 trigger: function(){ // 取出消息类型 var key = Array.prototype.shift.call(arguments) // 取出消息类型对应的回调函数集合 fns = this.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 = this.clientList[key] // 若是对应的消息没人订阅,直接返回 if(!fns) { return false } // 若是没有传入回调函数,表示取消key对应消息的全部订阅 if(!fn){ fns && (fns.length = 0) } else { for(var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l] if (_fn === fn) { fns.splice(l, 1) } } } } } var salesOffices = {} // 给对象安装发布-订阅功能 var installEvent = function(obj){ for(var i in event){ obj[i] = event[i] } } installEvent(salesOffices) salesOffices.listen('squareMeter88', fn1 = function(price){ console.log('价格1:'+price) }) salesOffices.listen('squareMeter87', fn2 = function(price){ console.log('价格2:'+price) }) salesOffices.trigger('squareMeter88', 20000) // 价格1: 20000 salesOffices.remove('squareMeter88') salesOffices.trigger('squareMeter88', fn1) // 打印结果为空
其实在这个发布-订阅模式中还存在两个问题:缓存
salesOffices
这个对象因此接下来实现一个全局的发布-订阅对象服务器
var Event = (function(){ var clientList = {}, listten, trigger, remove; listen = function(key, fn){ if(!clientList[key]){ clientList[key] = [] } clientList[key].push(fn) } trigger = function(){ var key = Array.prototype.shift.call(arguments) 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] if(!fns) { return false } if(!fn){ fns && (fns.length = 0) } else { for(var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l] if (_fn === fn) { fns.splice(l, 1) } } } } return { listen, trigger, remove } })() Event.listen('squareMeter88', fn1 = function(price){ // 订阅消息 console.log('价格1:'+price) }) Event.trigger('squareMeter88', 20000) // 价格1: 20000
咱们所了解的订阅发布都是订阅者必须先订阅一个消息,随后才能接受到发布者发布的消息。若是顺序反过来,发布者先发布一条消息,而此时没有订阅者,无疑消息会消失。架构
在某些状况下,咱们须要先讲这条消息保存下来,等到有对象订阅它的时候,再从新把消息发布给订阅者,若是QQ中的离线消息,离线消息先被保存在服务器中,接收人下次登录以后,能够重新接收到这条消息。app
再假如一个商城系统中,获取到用户信息后才能渲染导航模块,无疑这是一个异步的过程,不能保证ajax什么时候返回。因此咱们须要咱们发布订阅对象拥有先发布后订阅的能力。异步
为了知足这种须要,咱们要创建一个存放离线事件的堆栈,当事件发布的时候,若是此时尚未订阅者来订阅这个事件,咱们暂时把发布事件的动做包裹在一个函数里,这些包裹函数被存入堆栈中,等到终于有对象来订阅此事件,咱们将遍历堆栈而且依次执行这些包装函数,也就是从新发布里面的事件。固然离线事件的生命周期只有一次,就像QQ未读消息只会被从新阅读一次。异步编程
全局的订阅-发布对象里只有一个clientList来存放消息名和回调函数,咱们都经过它来订阅和发布各类消息,长此以往,不免会出现事件名冲突的状况,因此须要给Event对象提供建立命名空间的功能。函数
具体实现
var Event = (function() { var Event, _default = 'default'; Event = function() { var _listen, _trigger, _remove, _shift = [].shift, _unshift = [].unshift, namespaceCache = {}, _create, each = function(arr, fn) { var ret; for (var i = 0, l = arr.length; i < l; i++) { var n = arr[i]; ret = fn.call(n, i, n); } return ret; }; _listen = function(key, fn, cache) { if (!cache[key]) { cache[key] = []; } cache[key].push(fn); }; _remove = function(key, cache, fn) { if (cache[key]) { if (fn) { for (var i = cache[key].length - 1; i >= 0; i--) { if (cache[key][i] === fn) { // 删除订阅回调函数 cache[key].splice(i, 1); } } } else { cache[key] = []; } } }; _trigger = function() { var cache = _shift.call(arguments); var key = _shift.call(arguments); var args = arguments; var _this = this; var stack = cache[key]; if (!stack || !stack.length) { return; } return each(stack, function() { return this.apply(_this, args); }) }; _create = function(namespace) { namespace = namespace || _default; var cache = {}; var offlineStack = []; //离线事件 var ret = { listen: function(key, fn, last) { _listen(key, fn, cache); if (offlineStack === null) { return; } if (last === 'last') { offlineStack.length && offlineStack.pop()(); } else { each(offlineStack, function() { this(); }); } offlineStack = null; }, one: function(key, fn, last) { _remove(key, cache); this.listen(key, fn, last); }, remove: function(key, fn) { _remove(key, cache, fn); }, trigger: function() { var fn, args, _this = this; _unshift.call(arguments, cache); args = arguments; fn = function() { return _trigger.apply(_this, args); }; if (offlineStack) { return offlineStack.push(fn); } return fn(); } }; return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret; }; return { create: _create, one: function(key, fn, last) { var event = this.create(); event.one(key, fn, last); }, remove: function(key, fn) { var event = this.create(); event.remove(key, fn); }, listen: function(key, fn, last) { var event = this.create(); event.listen(key, fn, last); }, trigger: function() { var event = this.create(); event.trigger.apply(this, arguments); } } }(); return Event; }()); var fn1 = function(price) { console.log(price); }; // 实例 Event.listen('squareMeter88', fn1); Event.remove('squareMeter88', fn1); Event.listen('squareMeter88', function(price) { console.log('fn2: ' + price); }); Event.trigger('squareMeter88', 20000); // fn2: 20000
时间与对象之间的解耦,应用很是普遍,既能够用在异步编程中,也能够帮助咱们完成更松耦合的代码编写,从架构上看,不管MVC仍是MVVM都少不了发布-订阅的参与this
建立订阅者自己要消耗必定时间和内存,不管消息是否发生,一直存在内存中。虽然发布-订阅模式虽然能够弱化对象之间的联系,可是过分使用,对象之间的必然联系会被深埋,特别是多个订阅者和发布者嵌套会形成难以追踪的bug。