设计模式之发布 — 订阅模式
发布 — 订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
DOM 事件
实际上,只要我们曾经在 DOM
节点上面绑定过事件函数,那我们就曾经使用过发布 — 订阅模式,来看看下面这两句简单的代码发生了什么事情···
document.addEventListener('click',function(){
alert(1);
})
document.body.click();
在这里需要监控用户点击document.body
的动作,但是我们没办法预知用户将在什么时候点击。所以我们订阅 document.body
上的 click
事件,当body
节点被点击时,body
节点便会向订阅者发布这个消息。这很像购房的例子,购房者不知道房子什么时候开售,于是他在订阅消息后等待售楼处发布消息。
自定义事件
除了DOM
事件,我们还会经常实现一些自定义的事件,这种依靠自定义事件完成的发布 —订阅模式可以用于任何JavaScript
代码中。
现在看看如何一步步实现发布 — 订阅模式。
1.首先要指定好谁充当发布者(比如售楼处);
2.然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);
3.最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)。
var salesOffices = {}; //售楼部
salesOffices.clientList = {}; // 缓存列表
salesOffices.listen = function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
}
salesOffices.trigger = function(){
var key = Array.prototype.shift.call(arguments); // 取出消息key
var fns = this.clientList[key];
if( !fns || fns.length === 0 ){
return false;
}
for(var i=0; i<fns.length; i++){
fns[i].apply(this,arguments); // arguments是发布消息的的时候带的参数
}
}
// 测试
salesOffices.listen('seqaureMetar88', function(price){
console.log('seqaureMetar88 价格:' +price);
})
salesOffices.listen('seqaureMetar88', function(price){
console.log('zz seqaureMetar88 价格:' +price);
})
salesOffices.listen('seqaureMetar110', function(price){
console.log('seqaureMetar110 价格:' +price);
})
salesOffices.trigger('seqaureMetar88', 20000);
salesOffices.trigger('seqaureMetar110', 30000);
发布-订阅模式的通用实现
我们先抽出来放到一个对象中
var event = {
clientList: {},
listen: function(key,fn){
if(!this.clientList[key]){
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function(){
var key = Array.prototype.shift.call(arguments); // 取出消息key
var fns = this.clientList[key];
if( !fns || fns.length === 0 ){
return false;
}
for(var i=0; i<fns.length; i++){
fns[i].apply(this,arguments); // arguments是发布消息的的时候带的参数
}
}
}
在定义一个installEvent
函数,可以给所有函数安装发布-订阅功能(也可以用继承)
var installEvent = function(obj){
for(var i in event){
obj[i] = event[i]
}
}
// 测试
var salesOffices = {};
installEvent(salesOffices);
salesOffices.listen('seqaureMetar88', function(price){
console.log('seqaureMetar88 价格:' +price);
})
salesOffices.listen('seqaureMetar88', function(price){
console.log('zz seqaureMetar88 价格:' +price);
})
salesOffices.listen('seqaureMetar110', function(price){
console.log('seqaureMetar110 价格:' +price);
})
salesOffices.trigger('seqaureMetar88', 20000); // seqaureMetar88 价格:20000 seqaureMetar88 价格:20000
salesOffices.trigger('seqaureMetar110', 30000); // seqaureMetar110 价格:30000
小结
发布 — 订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布 — 订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。 从架构上来看,无论是MVC
还是MVVM
,都少不了发布 — 订阅模式的参与,而且JavaScript
本身也是一门基于事件驱动的语言。
当然,发布 — 订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布 — 订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug
不是件轻松的事情。