javascript 观察者模式的实现

原创
2018/08/27 23:09
阅读数 39

    简介:观察者模式在项目开发中非常实用,能很好的处理发布订阅问题。js中的发布订阅模式主要采用回调函数的方式来实现。

一、首先我们得定义个观察者对象类

     为它添加几个方法,添加某个要订阅的事件、删除某个要订阅的事件,观察者中需要包含一个订阅事件的对象属性events。

//定义一个观察者对象
	function Observer(name,sex,age){
		this.name=name;
		this.sex=sex;
		this.age=age;
		this.events={};//该观察者订阅的事件和回调函数
	}
	//添加点阅的方法,event表示订阅的事件,fun表示发布者触发的回调
	Observer.prototype.addEvent=function(event,fun){
		var events=this.events;
		//判断属性中是否有该key
		if(!events.hasOwnProperty(event)){
			events[event]=fun;
			this.events=events;//将事件赋值给当前观察者
			//把观察者添加到,发布者对象中
			var publisher=ObserverUtil.getPublisher(event);
			publisher.addObserver(event,this);
		}
	}
	//删除该观察者订阅的某事件
	Observer.prototype.removeEvent=function(event,fun){
		if(this.events.hasOwnProperty(event)){
			delete this.events[event];
			//把观察者从发布事件对象中删除
			var publisher=ObserverUtil.getPublisher(event);
			publisher.removeObserver(event,this);
		}
		//如果删除回调不空,执行删除回调函数
		if(fun){
			fun();
		}
	}

 

二、再来定义个发布者对象Publisher

    发布者对象定义一个事件属性event,表示发布的是什么事件,和一个观察者数组,用来收集所有订阅了该事件的观察者。在发布者类中定义三个方法,一个添加观察者、一个删除观察者、删除全部观察者、发布事件的方法。


	//定义一个发布者对象
	function Publisher(event){
		this.event=event;    //某事件名称
		this.observers=new Array();//观察者数组
	}
	//定义一个添加观察者的方法
	Publisher.prototype.addObserver=function(event,observer){
		if(event===this.event){//只有该事件是观察者订阅的事件,才能把观察者加入
			var observers=this.observers;
			var flag=true;
			if(observers && observers.length>0){
				for(var i=0;i<observers.length;i++){
					if(observers[i].name===observer.name){
						flag=false;
					}
				}
			}
			if(flag){
				observers.push(observer);	
			}
			this.observers=observers;
		}
	};
	//删除订阅了该发布对象的观察者
	Publisher.prototype.removeObserver=function(event,observer){
		if(event===this.event){//只有该事件是观察者订阅的事件,才能把观察者加入
			var observers=this.observers;
			for(var i=0;i<observers.length;i++){
				if(observers[i].name===observer.name){
					observers.splice(i,1);
					i--;
				}
			}
			this.observers=observers;
		}
	};
	//移除所有订阅了该事件的观察者
	Publisher.prototype.removeObserverAll=function(event){
		if(this.event==event){
			delete this.observers;
			console.log(this.observers);
		}
	}
	
	//发布方法
	Publisher.prototype.publish=function(fun){
		var result;
		if(fun){
			result=fun();//发布时要执行的函数
		}
		//遍历当前发布事件的观察者,通知观察者
		var observers=this.observers;
		if(observers && observers.length>0){
			for(var i=0;i<observers.length;i++){
				var events=observers[i].events;
				hasOwnProperty
				if(events.hasOwnProperty(this.event)){
					//将发布者中执行的结果返回给观察者
					events[this.event](result);
				}
			}
		}
	}

    三、采用闭包的方式,用一个观察者工具类ObserverUtil类封装代码

    在工具类中提供一个事件池属性、这里每一个发布事件对应一个发布者对象,把所有发布过的发布者对象都收集到该工具类中。在该工具类中提供对外访问的方法,和对事件池操作的方法。具体是发布事件方法、从事件池中获取一个发布者对象的方法、从事件池中删除一个事件、创建观察者方法。

window.ObserverUtil={
		eventPools:[],//事件池
		publish:function(event,fun){
			var p=this.getPublisher(event);
			if(!p){//当前事件不在事件池中
				p=new Publisher(event);//创建某事件
				this.eventPools.push(p);//把发布事件添加到发布池中
			}else{
				if(p.observers && p.observers.length>0 && fun){
					p.publish(fun);//发布某事件
				}
			}
			return p;//一般情况下,发布者不需要返回值
		},
		//从事件池中得到一个发布对象
		getPublisher:function(event){
			var eventPools=this.eventPools;
			for(var i=0;i<eventPools.length;i++){
				if(eventPools[i].event===event){
					return eventPools[i];
				}
			}
			return null;
		},
		//从事件池中删除一个事件
		removeEventPool:function(event){
			var eventPools=this.eventPools;
			for(var i=0;i<eventPools.length;i++){
				if(eventPools[i].event===event){
					//1,清楚该事件对应的所有观察者,关注的该事件
					this.removeObservers(event,eventPools[i].observers);
					//2,清除事件池该事件
					eventPools.splice(i,1);
					i--;
				}
			}
			this.eventPools=eventPools;
		},
		//清楚该事件对应的所有观察者,关注的该事件
		removeObservers:function(event,observers){
			for(var i=0;i<observers.length;i++){
				var events=observers[i].events;
				if(events.hasOwnProperty(event)){
					delete observers[i].events[event];
				}
			}		
		},
		//创建一个观察者
		newObserver:function(name,sex,age){
			return new Observer(name,sex,age);
		}
	};

四、演示代码

//发布事件,为了模拟采用循环发布
		var p=ObserverUtil.publish("华山论剑");
		var num=0;
		setInterval(function(){
			p.publish(function(){
				num++;
				return "明天前来华山论剑"+num;
			});
		},1000);
		
		//添加观察者
		var dx=ObserverUtil.newObserver("东邪","男",50);
		var xd=ObserverUtil.newObserver("西毒","男",50);
		var nd=ObserverUtil.newObserver("南帝","男",50);
		var bg=ObserverUtil.newObserver("北丐","男",50);
		var zbt=ObserverUtil.newObserver("周伯通","男",50);
		
		//观察者订阅事件
		dx.addEvent("华山论剑",function(msg){
			console.log(dx.name+msg);
		});
		xd.addEvent("华山论剑",function(msg){
			console.log(xd.name+msg);
		});
		nd.addEvent("华山论剑",function(msg){
			console.log(nd.name+msg);
		});
		bg.addEvent("华山论剑",function(msg){
			console.log(bg.name+msg);
		});
		zbt.addEvent("华山论剑",function(msg){
			console.log(zbt.name+msg);
		});
		
		//2s后,东邪取消华山论剑订阅
		setTimeout(function(){
			dx.removeEvent("华山论剑");
		},2000);
		
		//4s后,西毒取消订阅
		setTimeout(function(){
			xd.removeEvent("华山论剑");
		},4000);
		
		//6s后,南帝取消订阅
		setTimeout(function(){
			nd.removeEvent("华山论剑");
		},6000);
		
		//8s后,北丐取消订阅
		setTimeout(function(){
			bg.removeEvent("华山论剑");
		},8000);
		
		//10s后,周伯通取消订阅
		setTimeout(function(){
			zbt.removeEvent("华山论剑");
		},10000);
		
//		js实现观察者模式.html:34 东邪明天前来华山论剑1
//		js实现观察者模式.html:37 西毒明天前来华山论剑1
//		js实现观察者模式.html:40 南帝明天前来华山论剑1
//		js实现观察者模式.html:43 北丐明天前来华山论剑1
//		js实现观察者模式.html:46 周伯通明天前来华山论剑1
//		js实现观察者模式.html:37 西毒明天前来华山论剑2
//		js实现观察者模式.html:40 南帝明天前来华山论剑2
//		js实现观察者模式.html:43 北丐明天前来华山论剑2
//		js实现观察者模式.html:46 周伯通明天前来华山论剑2
//		js实现观察者模式.html:37 西毒明天前来华山论剑3
//		js实现观察者模式.html:40 南帝明天前来华山论剑3
//		js实现观察者模式.html:43 北丐明天前来华山论剑3
//		js实现观察者模式.html:46 周伯通明天前来华山论剑3
//		js实现观察者模式.html:40 南帝明天前来华山论剑4
//		js实现观察者模式.html:43 北丐明天前来华山论剑4
//		js实现观察者模式.html:46 周伯通明天前来华山论剑4
//		js实现观察者模式.html:40 南帝明天前来华山论剑5
//		js实现观察者模式.html:43 北丐明天前来华山论剑5
//		js实现观察者模式.html:46 周伯通明天前来华山论剑5
//		js实现观察者模式.html:43 北丐明天前来华山论剑6
//		js实现观察者模式.html:46 周伯通明天前来华山论剑6
//		js实现观察者模式.html:43 北丐明天前来华山论剑7
//		js实现观察者模式.html:46 周伯通明天前来华山论剑7
//		js实现观察者模式.html:46 周伯通明天前来华山论剑8
//		js实现观察者模式.html:46 周伯通明天前来华山论剑9
//		js实现观察者模式.html:46 周伯通明天前来华山论剑10
		
		//2s后发布另外一个订阅事件
		var p2=ObserverUtil.publish("天气预报");
		setTimeout(function(){
			var num2=0;
			setInterval(function(){
				p2.publish(function(){
					num2++;
					//模拟发布一个对象
					return {
						msg:"天气预报@",
						num:num2
					};
				});
			},1000);
		},2000);
		
		//观察者订阅事件
		dx.addEvent("天气预报",function(obj){
			console.log(dx.name+obj.msg+obj.num);
		});
		xd.addEvent("天气预报",function(obj){
			console.log(xd.name+obj.msg+obj.num);
		});
		nd.addEvent("天气预报",function(obj){
			console.log(nd.name+obj.msg+obj.num);
		});
		bg.addEvent("天气预报",function(obj){
			console.log(bg.name+obj.msg+obj.num);
		});
		zbt.addEvent("天气预报",function(obj){
			console.log(zbt.name+obj.msg+obj.num);
		});
		
		//4s 后移除所有订阅天气预报的观察者
		setTimeout(function(){
			ObserverUtil.removeEventPool("天气预报");
		},7000);//因为上面启动用了3s,所以这里设为7s,就刚好执行到4s的时候全部订阅者被移除
		
		//东邪天气预报@1
		//js实现观察者模式.html:122 西毒天气预报@1
		//js实现观察者模式.html:125 南帝天气预报@1
		//js实现观察者模式.html:128 北丐天气预报@1
		//js实现观察者模式.html:131 周伯通天气预报@1
		//js实现观察者模式.html:119 东邪天气预报@2
		//js实现观察者模式.html:122 西毒天气预报@2
		//js实现观察者模式.html:125 南帝天气预报@2
		//js实现观察者模式.html:128 北丐天气预报@2
		//js实现观察者模式.html:131 周伯通天气预报@2
		//js实现观察者模式.html:119 东邪天气预报@3
		//js实现观察者模式.html:122 西毒天气预报@3
		//js实现观察者模式.html:125 南帝天气预报@3
		//js实现观察者模式.html:128 北丐天气预报@3
		//js实现观察者模式.html:131 周伯通天气预报@3
		//js实现观察者模式.html:119 东邪天气预报@4
		//js实现观察者模式.html:122 西毒天气预报@4
		//js实现观察者模式.html:125 南帝天气预报@4
		//js实现观察者模式.html:128 北丐天气预报@4
		//js实现观察者模式.html:131 周伯通天气预报@4
		
		

五、观察者js完整代码

(function(window){
	
	//定义一个观察者对象
	function Observer(name,sex,age){
		this.name=name;
		this.sex=sex;
		this.age=age;
		this.events={};//该观察者订阅的事件和回调函数
	}
	//添加点阅的方法,event表示订阅的事件,fun表示发布者触发的回调
	Observer.prototype.addEvent=function(event,fun){
		var events=this.events;
		//判断属性中是否有该key
		if(!events.hasOwnProperty(event)){
			events[event]=fun;
			this.events=events;//将事件赋值给当前观察者
			//把观察者添加到,发布者对象中
			var publisher=ObserverUtil.getPublisher(event);
			publisher.addObserver(event,this);
		}
	}
	//删除该观察者订阅的某事件
	Observer.prototype.removeEvent=function(event,fun){
		if(this.events.hasOwnProperty(event)){
			delete this.events[event];
			//把观察者从发布事件对象中删除
			var publisher=ObserverUtil.getPublisher(event);
			publisher.removeObserver(event,this);
		}
		//如果删除回调不空,执行删除回调函数
		if(fun){
			fun();
		}
	}
	
	//定义一个发布者对象
	function Publisher(event){
		this.event=event;    //某事件名称
		this.observers=new Array();//观察者数组
	}
	//定义一个添加观察者的方法
	Publisher.prototype.addObserver=function(event,observer){
		if(event===this.event){//只有该事件是观察者订阅的事件,才能把观察者加入
			var observers=this.observers;
			var flag=true;
			if(observers && observers.length>0){
				for(var i=0;i<observers.length;i++){
					if(observers[i].name===observer.name){
						flag=false;
					}
				}
			}
			if(flag){
				observers.push(observer);	
			}
			this.observers=observers;
		}
	};
	//删除订阅了该发布对象的观察者
	Publisher.prototype.removeObserver=function(event,observer){
		if(event===this.event){//只有该事件是观察者订阅的事件,才能把观察者加入
			var observers=this.observers;
			for(var i=0;i<observers.length;i++){
				if(observers[i].name===observer.name){
					observers.splice(i,1);
					i--;
				}
			}
			this.observers=observers;
		}
	};
	//发布方法
	Publisher.prototype.publish=function(fun){
		var result;
		if(fun){
			result=fun();//发布时要执行的函数
		}
		//遍历当前发布事件的观察者,通知观察者
		var observers=this.observers;
		if(observers && observers.length>0){
			for(var i=0;i<observers.length;i++){
				var events=observers[i].events;
				hasOwnProperty
				if(events.hasOwnProperty(this.event)){
					//将发布者中执行的结果返回给观察者
					events[this.event](result);
				}
			}
		}
	}
	window.ObserverUtil={
		eventPools:[],//事件池
		publish:function(event,fun){
			var p=this.getPublisher(event);
			if(!p){//当前事件不在事件池中
				p=new Publisher(event);//创建某事件
				this.eventPools.push(p);//把发布事件添加到发布池中
			}else{
				if(p.observers && p.observers.length>0 && fun){
					p.publish(fun);//发布某事件
				}
			}
			return p;//一般情况下,发布者不需要返回值
		},
		//从事件池中得到一个发布对象
		getPublisher:function(event){
			var eventPools=this.eventPools;
			for(var i=0;i<eventPools.length;i++){
				if(eventPools[i].event===event){
					return eventPools[i];
				}
			}
			return null;
		},
		//从事件池中删除一个事件
		removeEventPool:function(event){
			var eventPools=this.eventPools;
			for(var i=0;i<eventPools.length;i++){
				if(eventPools[i].event===event){
					//1,清楚该事件对应的所有观察者,关注的该事件
					this.removeObservers(event,eventPools[i].observers);
					//2,清除事件池该事件
					eventPools.splice(i,1);
					i--;
				}
			}
			this.eventPools=eventPools;
		},
		//清楚该事件对应的所有观察者,关注的该事件
		removeObservers:function(event,observers){
			for(var i=0;i<observers.length;i++){
				var events=observers[i].events;
				if(events.hasOwnProperty(event)){
					delete observers[i].events[event];
				}
			}		
		},
		//创建一个观察者
		newObserver:function(name,sex,age){
			return new Observer(name,sex,age);
		}
	};
})(window);

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部