观察者模式又叫发布-订阅模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生该变时,所有依赖于它的对象都将得到通知。在JavaScript中,一般用事件模型来替代传统的观察者模式。
下面是售楼处(发布者)与各看房者(订阅者)的例子:
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),
fns = this.clientList[key];
if(!fns || fns.length == 0){ //没有绑定对应的消息
return false;
}
for(var i=0,fn; fn=fns[i++]){
fn.apply(this, arguments);
}
},
remove:function(key,fn){ //删除订阅
var fns = this.clientList[key];
if(!fns){ //如果key对应的消息没有被人订阅,则直接返回
return false;
}
if(!fn){ //如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
fns && (fns.length = 0);
}else{
for(var l=fns.length-1; l>=0; l--){ //反向遍历订阅的回调函数列表
var _fn = fns[l];
if(_fn ===fn){
fns.splice(l,1); //删除订阅者的回调函数
}
}
}
}
};
var installEvent = function(obj){ //给所有对象动态安装发布-订阅功能
for(var i in event){
obj[i] = event[i];
}
};
var salesOffices = {}; //定义售楼处
installEvent(salesOffices);
salesOffices.listen('squareMeter100',function(price){ // 张三订阅消息
console.log('价格=' + price);
});
salesOffices.listen('squareMeter150',function(price){ // 李四订阅消息
console.log('价格=' + price);
});
salesOffices.trigger('squareMeter100',2000000); // 输出 2000000
salesOffices.trigger('squareMeter150',3000000); // 输出 3000000
上面的代码还存在两个小问题:
1.每个发布者对象都添加了listen和trigger方法,以及一个缓存列表clientList,这是一种资源浪费
2.订阅者跟售楼处对象存在一定的耦合性,订阅者至少要知道售楼处对象的名字是salesOffices,才能订阅到事件
下面是对以上两个问题的改良:
var event = {
var clientList:[],
listen,
trigger,
remove;
listen = function(key,fn){
if(!clientList[key]){
clientList[key] = [];
}
clientList[key].push(fn); //订阅的消息添加进缓存列表
};
trigger = function(){ //发布消息
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if(!fns || fns.length == 0){ //没有绑定对应的消息
return false;
}
for(var i=0,fn; fn=fns[i++]){
fn.apply(this, arguments);
}
};
remove = function(key,fn){ //删除订阅
var fns = clientList[key];
if(!fns){ //如果key对应的消息没有被人订阅,则直接返回
return false;
}
if(!fn){ //如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
fns && (fns.length = 0);
}else{
for(var l=fns.length-1; l>=0; l--){ //反向遍历订阅的回调函数列表
var _fn = fns[l];
if(_fn ===fn){
fns.splice(l,1); //删除订阅者的回调函数
}
}
}
};
return {
listen:listen,
trigger:trigger,
remove:remove
};
};
Event.listen('squareMeter150',function(price){ // 李四订阅消息
console.log('价格=' + price);
});
Event.trigger('squareMeter150',2000000); // 输出 2000000
改良后,发布-订阅模式可以用一个全局的Event对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不需要了解消息会推送给哪些订阅者,Event作为类似“中介者”的角色,把订阅者和发布者联系起来。
观察者模式的优点非常明显,一为时间上的解耦,二为对象间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以用来编写更松耦合的代码编写。但也不是没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息始终都没发生,但这个订阅者会始终存在于内存中。另外,观察者模式虽然可以弱化对象间的联系,但如果过度使用的话,对象间的必要联系也将被深藏在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个bug不是键轻松的事。