订阅发布模式它定义对象间是一种一对多的依赖关系,当一个状态发生改变的时候,所有依赖它的对象都将得到通知。
那么订阅发布模式在项目开发当中有什么用呢?
1、运用于异步编程,我们经常异步编程使用回调函数来解决问题,其实订阅发布模式也可解决异步的问题。
2、可以取代对象之间硬编码的通知机制。一个对象不用再显式的调用另一个对象的接口,订阅发布模式实现了对象和对象之间的松耦合的关系。
再者如何通过代码封装实现订阅发布模式呢?如何实现自定义订阅发布模式呢?
–自定义事件类型=>订阅者可以只订阅自己感兴趣的事件类型了
var salesOffices={}; //定义对象用来发布,订阅
salesOffices.clientList ={}; //缓存列表,存放订阅者的回调函数
salesOffices.listen=function(key,fn){ //订阅,传一个变量和回调函数.
if(!this.clientList[key]){
this.clientList[key]=[] //如果还没有订阅过此类消息,给该类消息创建一个缓存列表
}
this.clientList[key].push(fn); //订阅的消息添加进消息缓存列表
};
salesOffices.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++];){ //arguments是发布时附送的参数,待订阅者接收.
fn.apply(this,arguments);
}
}
//使用:
salesOffices.listen('squ88',function(value){ //先订阅
console.log(value)
})
salesOffices.trigger('squ88',2000) //发布了
–定义局部的event:
var event={
clientList:{},
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(fn,arguments);
}
},
remove:function(key,fn){
var fns=this.clientList[key];
if(!fns){ //如果key对应的消息没有被人订阅,则直接返回
return false;
}
if(!fn){ //如果没有传入对调函数,表示需要取消key对应消息的所以订阅
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 installEvent=function(obj){
for(var i in event){
obj[i]=event[i];
}
}
var sal={};
installEvent(sal);
sal.listen('ssalqu88',function(value){ //先订阅
console.log(value)
})
sal.trigger('ssalqu88',9000) //发布了
如上代码:实现局部的订阅发布模式,如果还有其他对象需要,则上面的代码还需在写一次.所以要有一个办法让所有的对象都拥有订阅发布模式。installEvent用来安装局部的event有订阅发布的对象----浅拷贝:已拷贝生成的对象处理如果修改了函数,例如trigger会造成该功能丢失,当前浅拷贝的话就是引用了event对象的功能,对使用者来说不会任意去修改,浪费资源。
–定义全局的Event:
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=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:listen,
trigger:trigger,
remove:remove
}
}())
那么上面的代码其实还存在问题的?
首先:全局的event对象相当于中介者,这样的情况会造成实际类型的变量名冲突。
再者:订阅发布模式一定得先订阅在发布吗?其实并不是的。订阅发布模式也是可以先发布后订阅,这个情况可以想想离线接收qq,vx信息。但是这个信息的生命周期就只有一次。
所以接下来的代码就是解决如上的问题。
var Event=(function(){
var global =this,
Event,
_default='default';
Event=function(){
var _listen,
_trigger,
_remove,
_slice=Array.prototype.slice,
_unshift=Array.prototype.unshift,
_shift=Array.prototype.shift,
namespaceCache={},
_create,
find,
each=function(ary,fn){
var ret;
for(var i=0,l=ary.length;i<l;i++){
var n=ary[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;i>=0;i--){
if(cache[key][i]===fn){
cache[key].splice(i,1)
}
}
}else{
cache[key]=[];
}
}
};
_trigger=function(){
var cache=_shift.call(arguments),
key=_shift.call(arguments),
args=arguments,
_self=this,
ret,
stack=cache[key];
console.log(cache,stack)
if(!stack||!stack.length){
return ;
}
return each(stack,function(){
return this.apply(_self,args);
})
};
_create=function(namespace){
var namespace=namespace||_default;
var cache={};
var offlineStack=[]; //离线事件
ret={
listen:function(key,fn,last){
_listen(key,fn,cache)
if(offlineStack===null){
return ;
}
if(last==='last'){
offlineStack.length && offlineStack.pop()();
}else{
console.log(offlineStack)
each(offlineStack,function(){ //循环遍历离线事件的数组,并且执行该数组当中的每一函数
this()
})
}
offlineStack=null; //全部监听之后,将离线数组设为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,_self=this;
_unshift.call(arguments,cache); //将cache对象添加在函数参数的头部
args=arguments;
console.log(arguments,offlineStack)
// fn为发布的函数
fn=function(){
return _trigger.apply(_self,args); //执行发布函数,并将key,回调函数,cache对象作为参数传递
}
// 如果先发布,那个offlineStack不为null,则将fn添加在离线数组里,待订阅时再执行该函数
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;
})()
Event.listen('onmove',function(b){
console.log(b)
})
Event.trigger('onmove',100);
总结:
发布订阅模式是一个对象与多个不同对象之间进行松耦合,是一个对象对多个对象的过程。一个对象改变,所有依赖它的对象都会做出改变。发布订阅模式实现过程最关键的是有个缓存列表,将订阅方存放在缓存列表当中,等到发布方发布内容,依赖这个发布方的订阅方即可收到信息。订阅方和发布方是通过相同的名称(可以是任何命名名称)进行相互依赖,发布方通过调用订阅方的函数进行传值的一个过程。订阅方通过声明的函数接收信息。
发布订阅暴露一个对象:对象有订阅和发布函数。
1、先订阅后发布
订阅方先订阅,等到一定时机发布方发布消息,订阅方可接收信息。
一般应用于异步编程,比如http请求完成之后回来的数据要传给某个对象。
2、先发布后订阅
先发布后订阅的场景比如qq离线了。对方好友发消息,等你登入qq可接收信息。离线接收qq信息,只能接收一次。
关键有离线列表,将发布的信息保存起来,等到订阅了收到数据。
https://segmentfault.com/a/1190000018803615#item-2-4