观察者模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
以学生为例,上课和下课的铃声就是被观察者,学生就是观察者,当下课铃声响了,学生就知道下课了,就出去跑着玩了,然后过了10分钟,上课铃声又响了,然后学生听到上课铃,又开始从外面往教室跑,去上课。
其实我们在平时也用到过观察者模式,只是我们没有注意到而已,举一个简单的例子:我们曾经在DOM节点上面绑定过事件函数,那我们就使用过观察者模式,因为JS和DOM之间就是实现了一种观察者模式。
document.body.addEventListener("click", function() {
alert("Hello World")
},false )
document.body.click() //模拟用户点击
以上js就是观察者,DOM就是被观察者,给DOM添加点击事件就相当于订阅了DOM,当DOM被点击,DOM就会通知js触发“ alert(“Hello World”) ”。
我们作为前端开发人员,肯定知道vue是一个MVVM模式的框架;vue的核心就是双向绑定,那么双向绑定的实现实际上就是一种观察者模式;因为你首先绑定了一个数据之后(订阅方法),浏览器并不知道你什么时候修改,你页面上所有绑定了该数据或者依赖该数据的节点其实就是一个预订列表,只有等你修改了该数据的值的时候,vue才会通知(发布方法)到依赖该数据的方法/数据进行相应的操作或刷新;
下面我们模仿一下vue的v-model双向绑定的实现:
// 创建对象
var targetObj = {
name:'小李'
}
var targetObj2 = {
name:'小李'
}
// 定义值改变时的处理函数(观察者)
function observer(oldVal, newVal) {
// 其他处理逻辑...
targetObj2.name = newVal
console.info('targetObj2的name属性的值改变为 ' + newVal);
}
// 定义name属性及其set和get方法(name属性为被观察者)
Object.defineProperty(targetObj, 'name', {
enumerable: true,
configurable: true,
get: function() {
return name;
},
set: function(val) {
//调用处理函数
observer(name, val)
name = val
}
});
targetObj.name = '张三';
targetObj.name = '李四';
console.log(targetObj2.name)
当然观察者模式绝不仅限于这俩种实现场景,在我们的生活中、业务场景中有很多观察者模式的示例。
我们也知道,设计模式是一种解决问题的思路而非一种固定的公式,那么我们怎么实现观察者模式呢?
//定义商家
var merchants = {
};
//定义预订列表
merchants.orderList = {
};
//将增加的预定者添加到预订列表中 (订阅方法)
merchants.listen = function(id,info){
//如果存在
if(!this.orderList[id]){
// (预订列表)
this.orderList[id] = [];
}
// 将用户的预定的产品信息存入到数组中
this.orderList[id].push(info);
}
//发布信息(发布方法)
merchants.publish = function(){
var id = Array.prototype.shift.call(arguments);
var infos = this.orderList[id];
if(!infos || infos.length === 0){
console.log("您还没有预定信息");
return false;
}
for(var i = 0;i < infos.length;i++){
console.log("预定成功");
console.log("尊敬的用户:")
infos[i].apply(this,arguments);
console.log("到货了");
}
}
//取消订阅
merchants.remove = function(id,fn){
var infos = this.orderList[id];
if(!infos){
return false}
if(!fn){
console.log(123);
}else{
for(var i = 0;i < infos.length;i++){
if(infos[i] === fn){
infos.splice(i,1);
}
}
}
}
let customeA = function(){
console.log("黑色尊享版一台");
}
merchants.listen("135xxxxxxxx",customeA);
merchants.remove("135xxxxxxxx",customeA);
merchants.publish("135xxxxxxxx");
上面的代码稍微有点长,不过没有很难;首先我们是定义了一个对象是作为商家,然后定义了一个空的对象作为预订列表,再一步步的实现我们的订阅方法和发布以及取消订阅的方法;逻辑不复杂
发布方法中的var id = Array.prototype.shift.call(arguments);
这句的意思是将merchants.publish("135xxxxxxxx");
方法调用的时候第一个参数返回给它然后复制为id,所以其实此时的id值为"135xxxxxxxx"
;
infos[i].apply(this,arguments);
这个方法也不是特别好理解,首先infos里装的是预订者的手机号码以及手机版本信息,所以我们infos[i].apply(this,arguments);
这个方法其实就是将"135xxxxxxxx"
对应的手机版本信息函数进行调用了一遍;实际上等于infos[i](arguments);
观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
有些人认为观察者模式就是发布订阅模式,但实际上观察者模式和发布订阅模式是有区别的。
区别:观察者模式只有两个,一个是观察者一个是被观察者。发布订阅模式不一样,发布订阅模式还有一个中间层,发布订阅模式的实现是,发布者通知给中间层 => 中层接受并通知订阅者 => 订阅者收到通知并发生变化
上图左边是观察者模式右边是发布订阅模式
往更深层次讲:
观察者和被观察者,是松耦合的关系
发布者和订阅者,则完全不存在耦合
从使用层面上讲:
观察者模式,多用于单个应用内部
发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件