简介:观察者模式在项目开发中非常实用,能很好的处理发布订阅问题。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);