简介
发布-订阅模式也可称为观察者模式,使用非常广泛,也非常有用。通过发布者遍历依赖通知,监听者监听添加依赖。在Vue2.x中的响应式原理也是通过发布-订阅模式实现
DOM事件
非常常用的DOM事件监听就是发布-订阅模式的实现。
document.body.addEventListener('click',function(){
alert('点击');
},false)
我们不知道事件什么时候会被出触发,所以使用监听的方式进行监听,当触发该事件的时候就会调用回调函数。
自定义事件
很多时候,我们不是想监听事件,而是监听某种操作结果,在某个时候触发,可以自己实现类似监听器的功能。
const m = {};
m.deps = [];//依赖数组
m.listen = function(dep){
this.deps.push(dep);
}
m.trigger = function(){
for(let i = 0;i<this.deps.length;i++){
this.deps[i].apply(this,arguments);
}
}
// 订阅者1
m.listen(function(){
console.log('添加订阅1');
})
// 订阅者2
m.listen(function(){
console.log('添加订阅2');
})
m.trigger();// 发布者
m.trigger();// 发布者
上述代码就是一个简单的发布-订阅模式的实现。但是上面实现任然存在一个缺点。订阅者1订阅消息1,订阅者2订阅消息2,但是触发的时候订阅者1会接收到消息2,订阅者2会接收到消息1,这在根本上是错误的。所以需要实现一个更加通用的发布-订阅模式。原理就是添加key进行区分。
const m = {
deps:{}
};
m.listen = function(key,dep){
if(!this.deps[key]){
this.deps[key] = [];
}
this.deps[key].push(dep);
}
m.trigger = function(key,fn){
const fns = this.deps[key];
if(!fns||fns.length===0){
return false
}
for(let i = 0;i<fns.length;i++){
fns[i].apply(this,arguments);
}
}
// 订阅者1
m.listen('订阅者1',function(){
console.log('添加订阅1');
})
// 订阅者2
m.listen('订阅者2',function(){
console.log('添加订阅2');
})
m.trigger('订阅者1');// 发布者
m.trigger('订阅者2');// 发布者
result:
添加订阅1
添加订阅2
在此基础上,可以添加移除订阅功能
m.remove = function(key,fn){
const fns = this.deps[key];
if(!fns||fns.length===0){
return false
}
if(!fn){
fns.length = 0;
return
}
for(let i=0;i<fns.length;i++){
if(fns[i]===fn){
fns.splice(i,1);
break
}
}
}
通用的发布-订阅模式
上述代码的实现虽说可以完成目的,但是还是存在问题的,假如现在我们想为其他对象添加发布-订阅功能,那么要把全部代码复制粘贴一遍?显然是不好的,所以需要设计一个通用的发布-订阅模式
const Event = {
deps:{},
listen(key,dep){
if(!this.deps[key]){
this.deps[key] = [];
}
this.deps[key].push(dep);
},
trigger(key,fn){
const fns = this.deps[key];
if(!fns||fns.length===0){
return false
}
for(let i = 0;i<fns.length;i++){
fns[i].apply(this,arguments);
}
},
remove(key,fn){
const fns = this.deps[key];
if(!fns||fns.length===0){
return false
}
if(!fn){
fns.length = 0;
return
}
for(let i=0;i<fns.length;i++){
if(fns[i]===fn){
fns.splice(i,1);
break
}
}
}
};
function insEvent (obj){
for(const event in Event){
if(Event.hasOwnProperty(event)){
obj[event] = Event[event];
}
}
}
const obj1 = {};
const obj2 = {};
insEvent(obj1);
insEvent(obj2);
先发布后订阅
传统的发布-订阅模式都是先订阅,然后发布者发布事件。但是有时任然会存在需要先发布后订阅的场景,比如离线事件,我们可以先将事件进行缓存,等待真正监听的时候进行调用,下面是先发布后订阅的简单实现
const Event = (function(){
const cache = {};
return {
listen(key,callback){
fns = cache[key];
if(!fns||fns.length===0){
return false
}
for(let i =0;i<fns.length;i++){
callback(fns[i].apply(this,arguments));
}
},
trigger(){
const key = Array.prototype.shift.apply(arguments,arguments);
const value = Array.prototype.shift.apply(arguments,arguments);
const fn = function(){
return value
}
if(!cache[key]){
cache[key] = [];
}
cache[key].push(fn);
}
}
})()
// 先发布后订阅,离线缓存等
Event.trigger('click',1);
Event.trigger('click',2);
Event.listen('click',function(res){
console.log(res);// 1 2
})
总结
发布-订阅模式是非常有用的设计模式,在架构中也不少见,其优点在于减少了时间上的耦合,和对象之间的耦合,但是也可能是造成缺点的原因,耦合性太低不一定是好事。。对象之间的联系可能不容易发现,对后期维护造成不必要的开销