: :其他软件 2020-09-07 11:07:12
代码深入了解实现
1.简单实现
通过上面的例子,我们应该大致了解了发布-订阅者模式,那我们用代码模拟实现以下这个模式:
实现步骤:
先指定谁是发布者(售楼处)
然后给发布者添加一个缓存列表,用来存放回调函数以便通知订阅者(售楼处的花名册)
最后发布消息的时候,发布者遍历这个缓存列表,依次触发里面缓存的订阅函数(遍历花名册,发送售房消息)
另外,还可以在发布消息时,在回调函数里添加一些参数,订阅者可以接受到这些参数。这是非常必要的,比如售楼处可以在发给订阅者短信里加上房子的单价,面积等信息,订阅者可以接受到这个消息是进行各自的处理。
let salesOffice = {};//售楼处
salesOffice.clientList = [];//缓存列表,存放订阅者的回调函数(花名册)
salesOffice.listen = function(fn){//将订阅者添加到缓存列表
this.clientList.push(fn);
}
salesOffice.trigger = function(){//发布消息
if(!this.clientList){
return ;
}
for(let i = 0; i < this.clientList.length; ++i){//遍历缓存列表
let fn = this.clientList[i];
fn.apply(this, arguments);//arguments:发布消息带上的参数
}
}
salesOffice.listen((squareMeter,price)=>{//小明订阅的消息
console.log('面积=' + squareMeter);
console.log('价格=' + price);
})
salesOffice.listen((squareMeter,price)=>{//小红订阅的消息
console.log('面积=' + squareMeter);
console.log('价格=' + price);
})
salesOffice.trigger(88, 10000);//售楼处发布的消息
salesOffice.trigger(66, 20000);//售楼处发布的消息
到这里,我们已经实现了简单的发布-订阅模式。但这里还存在一些问题。订阅者接受到了发布者发布的所有消息,虽然小明只想购买88平米的房子,但发布者依然将66平米的房子消息也推送给了小明。这对小明来说是不必要的困扰,所有我们添加一个key值让订阅者,订阅他所感兴趣的消息。
let salesOffice = {};//售楼处
salesOffice.clientList = {};//缓存列表,存放订阅者的回调函数(花名册)
salesOffice.listen = function(key, fn){//将订阅者添加到缓存列表
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
}
salesOffice.trigger = function(){//发布消息
let key = Array.prototype.shift.call(arguments);//取出参数中的key值
let fns = this.clientList[key];
if(!fns || !fns.length){//没有订阅这个消息就返回
return false;
}
for(let i = 0; i < fns.length; ++i){
let fn = fns[i];
fn(...arguments);
}
}
salesOffice.listen('squareMeter88', (price)=>{
console.log('价格=' + price);
});
salesOffice.listen('squareMeter66', (price)=>{
console.log('价格=' + price);
})
salesOffice.trigger('squareMeter88', 20000);
有没有办法让所有的对象都有发布-订阅功能呢?javascript作为一门解释执行的语言,给对象添加职责是理所当然的事情,将功能提前出来,放在一个单独的对象内。
let Event = {
clientList: {},
listen: function(key, fn){
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function(){
let key = Array.prototype.shift.call(arguments);
let fns = this.clientList[key];
if(!fns && !fns.length){
return ;
}
for(let i = 0; i < fns.length; ++i){
let fn = fns[i];
fn(...arguments);
}
}
}
let intallEvent = function(obj){
for(let i in Event){
obj[i] = Event[i];
}
return obj;
}
let salesOffice = {};
intallEvent(salesOffice);
salesOffice.listen('squareMeter', (price)=>{
console.log('价格=' + price);
})
salesOffice.trigger('squareMeter', 20000)
有时候也需要取消订阅,当小明突然不想买房子了,避免继续接受到售楼处发过来的信息,小明需要取消之前订阅的消息,现在给Event添加remove方法。
let Event = {
clientList: {},
listen: function(key, fn){
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function(){
let key = Array.prototype.shift.call(arguments);
let fns = this.clientList[key];
if(!fns || !fns.length){
return ;
}
for(let i = 0; i < fns.length; ++i){
let fn = fns[i];
fn(...arguments);
}
},
remove: function(key, fn){
let fns = this.clientList[key];
if(fns){
if(fn){
for(let i = fns.length - 1; i >= 0; --i){
if(fns[i] === fn){
fns.splice(i, 1);
}
}
}else{
fns.length = 0;
}
}
}
}
let installEvent = function(obj){
for(let i in Event){
obj[i] = Event[i];
}
}
let salesOffice = {};
installEvent(salesOffice);
salesOffice.listen('squareMeter88', (price)=>{
console.log('价格=' + price);
})
salesOffice.trigger('squareMeter88', 20000);
salesOffice.remove('squareMeter88');
salesOffice.trigger('squareMeter88', 20000);
2.全局发布订阅对象
刚刚实现的发布-订阅模式,依然有一些问题:1.给每个发布者对象添加listen,trigger,remove等属性,这其实是一种资源的浪费。2.小明和售楼处依然保持一定的耦合性,小明至少需要知道售楼处的名字(salesOffice),才能订顺利订阅到事件。
salesOffice.listen('squareMeter88', (price)=>{
console.log('价格=' + price);
})
如果小明想订阅200平米的房子,而这个房子的卖家是salesOffice2,这意味这小明需要订阅salesOffice2对象。
salesOffice2.listen('squareMeter88', (price)=>{
console.log('价格=' + price);
})
其实现实中,买房子未必需要亲自去售楼处,只要将订阅的请求交给中介公司,而各大房产公司也只需要通过中介公司发布房子的信息,这样一来,并不用关心消息是来自哪个公司,在意的是是否能顺利的收到消息。为了保证订阅者和发布者能正常通讯,订阅者和发布者必须要知道这个中介公司。
可以使用一个全局的Event对象来实现,订阅者不需要知道消息是来自那个发布者,发布者不需要知道消息推送给了哪个订阅者,Event作为一个类似"中介"的角色,将订阅者和发布者联系在了一起。
let Event = (function(){
let clientList = {},
listen,
trigger,
remove;
listen = function(key, fn){
if(!clientList[key]){
clientList[key] = [];
}
clientList[key].push(fn);
}
trigger = function(){
let key = Array.prototype.shift.call(arguments);
let fns = clientList[key];
if(!fns || !fns.length){
return ;
}
for(let i = 0; i < fns.length; ++i){
let fn = fns[i];
fn(...arguments);
}
}
remove = function(key, fn){
let fns = clientList[key];
if(fns){
if(fn){
for(let i = fns.length - 1; i >= 0; --i){
if(fns[i] === fn){
fns.splice(i, 1);
}
}
}else{
fns.length = 0;
}
}
}
return {
listen: listen,
trigger: trigger,
remove: remove
};
})();
Event.listen('squareMeter88', (price)=>{
console.log('价格=' + price);
});
Event.trigger('squareMeter88', 20000);
Event.remove('squareMeter88');
Event.trigger('squareMeter88', 30000);
模块通讯(高频面试考点)
上面实现了发布-订阅模式,它是基于Event全局对象的,可以利用它将两个封装良好的模块中进行通讯,这两个模块可以完全不知道对方的存在。
比如现在有两个模块,a模块有一个点击按钮,每次点击后,b模块的div中会显示按钮被点击的次数,使用发布-订阅模式,使得a,b模块可以在不改变封装性的条件下进行通讯。
TAG: 代码,实现
10-13NRF24L01发送代码
10-13《游戏编程All In One》代码
10-03excel实现多条件排序功能详解
10-03在Excel中实现一个随机滚动
10-03excel复制粘贴命令实现行列互换
10-03excel实现摄影功能图解
10-03excel表格中的数据实现快速查询
08-31excel实现自动快速编号方法
08-31Excel如何实现隔列输入
08-28Excel表格数据实现隔列粘贴方法
08-28word文档中实现图文并茂生动形象方法