JavaScript实现与使用发布/订阅模式详解
本文实例讲述了JavaScript实现与使用发布/订阅模式。分享给大家供大家参考,具体如下:
一、发布/订阅模式简介
发布/订阅模式(即观察者模式):设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个订阅者对象订阅发布者对象的特定活动,并在发布者对象的状态发生改变后,订阅者对象获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者,并且可能经常以事件对象的形式传递消息。
基本思路:发布者对象需要一个数组类型的属性,以存储所有的订阅者。订阅(即注册)就是将新的订阅者加入到这个数组中去,而注销即是从这个数组中删除某个订阅者。此外,发布消息就是循环遍历订阅者列表并通知他们。
二、如何发布订阅者的方法?
这里我的大体思路是对的。不过当时面试时,我还说了“在发布者之外,还需要定义了一个新的类——订阅者。在订阅者中,需要定义了一个类似getNews的方法,以便在发布者发布消息时,调用该方法”。然后,面试官说这样太麻烦了,万一订阅者没有这个方法呢?然后,我不是很懂……
于是我就想到了,在发布消息时直接传递了参数:obj.news=msg;然后面试官说这样不是更麻烦了吗?这样的话,如果订阅者没有news这个属性怎么办?还得判断订阅者是否有news这个属性,没有的话就会出现undifined的报错。
然后,我就不知道该怎么做了……然后面试官为人特别nice,告诉我“可以用继承或者是在注册时候就传入一个function”。
面试完后,回家上网查相关知识,整理出的注意点如下:
- 发送消息,即通知,意味着调用订阅者对象的某个方法。故当用户订阅信息时,该订阅者需要向发布者的subscribe()提供它的其中一个方法——这应该就是面试官所说的注册时候就传入一个方法。
- 每个发布者对象需要具有以下成员:
subscribe():注册/订阅,将订阅者添加到subscribers数组中;
unsubscribe():取消订阅。从subscribers数组中删除订阅者;
publish():循环遍历subscribers数组中的每一个元素,并且调用它们注册时所提供的方法;
所有这三种方法都需要一个type参数。这是因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸),而订阅者可能仅选择订阅其中一种,而另外一种不订阅。
三、代码实现
参考《JavaScript模式》一书,使用字面量实现代码如下:
//由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。
//如下实现一个通用发布者,定义发布者对象……
letpublisher={
subscribers:{
any:[]
},
subscribe:function(fn,type=`any`){
if(typeofthis.subscribers[type]===`undefined`){
this.subscribers[type]=[];
}
this.subscribers[type].push(fn);
},
unSubscribe:function(fn,type=`any`){
letnewSubscribers=[];
this.subscribers[type].forEach((item,i)=>{
if(item!==fn){
newSubscribers.push(fn);
}
});
this.subscribers[type]=newSubscribers;
},
publish:function(args,type=`any`){
this.subscribers[type].forEach((item,i)=>{
item(args);
});
}
};
//定义一个函数makePublisher(),它接受一个对象作为参数,通过把上述通用发布者的方法复制到该对象中,从而将其转换为一个发布者
functionmakePublisher(obj){
for(letiinpublisher){
if(publisher.hasOwnProperty(i)&&typeofpublisher[i]===`function`){
obj[i]=publisher[i];
}
}
obj.subscribers={any:[]};
}
//实现paper对象
varpaper={
daily:function(){
this.publish(`bignewstoday!`);
},
monthly:function(){
this.publish(`interestinganalysis`,`monthly`);
}
};
//将paper构造成一个发布者
makePublisher(paper);
//看看订阅对象joe,该对象有两个方法:
varjoe={
drinkCoffee:function(paper){
console.log(`Justread`+paper);
},
sundayPreNap:function(monthly){
console.log(`Abouttofallasleepreadingthis`+monthly);
}
};
//paper注册joe(即joe向paper订阅)
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap,`monthly`);
//即joe为默认“any”事件提供了一个可被调用的方法,而另一个可被调用的方法则用于当“monthly”类型的事件发生时的情况。现在让我们来触发一些事件:
paper.daily();//Justreadbignewstoday
paper.daily();//Justreadbignewstoday
paper.monthly();//Abouttofallasleepreadingthisinterestinganalysis
paper.monthly();//Abouttofallasleepreadingthisinterestinganalysis
paper.monthly();//Abouttofallasleepreadingthisinterestinganalysis
我自己又尝试用ES6的class语法写了一遍(PS:这是转载者自己写的,原文作者是用函数自己又实现了一遍):
//由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。
//如下实现一个通用发布者,定义发布者对象……
classPublisher{
constructor(){
this.subscribers={
any:[]
}
}
subscribe(fn,type=`any`){
if(typeofthis.subscribers[type]===`undefined`){
this.subscribers[type]=[];
}
this.subscribers[type].push(fn);
}
unSubscribe(fn,type=`any`){
letnewArr=[];
this.subscribers[type].forEach((item,i)=>{
if(item!==fn){
newArr.push(fn);
}
});
this.subscribers[type]=newArr;
}
publish(args,type=`any`){
this.subscribers[type].forEach((item,i)=>{
item(args);
});
}
//定义一个函数makePublisher(),它接受一个对象作为参数,通过把上述通用发布者的方法复制到该对象中,从而将其转换为一个发布者
staticmakePublisher(obj){
obj.publisher=newPublisher();
}
}
//实现person对象
varperson={
sayHi:function(name){
this.publisher.publish(name);
},
sayAge:function(num){
this.publisher.publish(num,`age`);
}
}
//将person构造成一个发布者
Publisher.makePublisher(person);
//看看订阅对象myLover,该对象有两个方法:
varmyLover={
name:``,
hello:function(name){
this.name=name;
console.log(`Hi,iam`+name+`!Nicetomeetyou!`);
},
timeOfLife:function(num){
console.log(`Hello!Mynameis`+this.name+`!Iam`+num+`yearsold!`);
}
}
//person注册myLover(即myLover向person订阅)
person.publisher.subscribe(myLover.hello);
person.publisher.subscribe(myLover.timeOfLife,`age`);
//即myLover为默认“any”事件提供了一个可被调用的方法,而另一个可被调用的方法则用于当“age”类型的事件发生时的情况。现在让我们来触发一些事件:
person.sayHi(`Jimmy`);//Hi,iamJimmy!Nicetomeetyou!
person.sayAge(24);//Hello!MynameisJimmy!Iam24yearsold!
person.sayHi(`Tom`);//Hi,iamTom!Nicetomeetyou!
person.sayAge(6);//Hello!MynameisTom!Iam6yearsold!
person.sayAge(18);//Hello!MynameisTom!Iam18yearsold!
更多关于JavaScript相关内容还可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。