js订阅发布模式

订阅发布模式它定义对象间是一种一对多的依赖关系,当一个状态发生改变的时候,所有依赖它的对象都将得到通知。
那么订阅发布模式在项目开发当中有什么用呢?
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

猜你喜欢

转载自blog.csdn.net/Miss_hhl/article/details/103667436
今日推荐