共计 3048 个字符,预计需要花费 8 分钟才能阅读完成。
观察者模式又名 ” 公布 - 订阅 ” 模式,可能无效的将模块间进行解耦。
观察者模式是使用在一些有一对多关系的场景中,一个对象有了一些扭转,其余依赖于该对象的对象也要做一些动作。
比方,上课,老师提了一个问题,所有会这道问题的学生都解答这个问题。其中老师就是主对象,老师发问就是状态扭转,学生是依赖于老师的对象,学生答复问题就是针对老师发问这个状态所做的扭转。
弄清楚这个原理,咱们来看看怎么用代码实现。
咱们能够创立一个对象帮咱们监控主对象得扭转,而且要告诉订阅了主对象扭转这个工夫的依赖对象做出相应,还以下面老师发问学生答为例,咱们创一个 Observer 的对象,它得能帮学生去订阅老师发问某个问题,也得能帮老师收回发问得音讯,而且既然有订阅,就得有解除订阅,所以得有一个解除订阅得办法,基于这个思路,咱们设计的 Observer 差不多就是以下这个样子:
const Observer = (function(){var _messages = {};
return {add: ()=>{}, // 订阅音讯
emit: ()=>{}, // 公布音讯
remove: ()=>{} // 勾销音讯的订阅
}
})()
用立刻执行函数创立一个公有变量,_messages 寄存被订阅的音讯,以及订阅者所要执行的操作。
接下来咱们看 add 办法,add 要把被订阅的音讯,以及订阅者的操作寄存到 _messages 里,以供在音讯收回时,订阅者可能做出相应回应。所以 add 办法能够这么实现:
add: (type, fn)=>{ //type 就是音讯,fn 就是订阅者的回调
if(_messages[type]){_messages[type].push(fn)
}else{_messages[type] = [fn]
}
}
办法很简略,就是看_messages 里有没有某个音讯,如果没有就创立数组,把回调方里,否者间接放数组里。因为可能有多个订阅者订阅同一个音讯,所以要用数组。
接下来是 emit 办法,emit 用于收回某个音讯,其实就是去_messages 中看某个音讯有没有订阅者,有的话就把所有的回调执行一遍。
emit: (type, args)=>{if(_messages[type] && _messages[type] instanceof Array){for(let i = 0; i < _messages[type].length; i++){typeof _messages[type][i] == 'function' && _messages[type][i].call(this, args)
}
}
}
最初就是 remove 办法,其实就是看某个音讯找某个订阅者的回调,删掉就行。
remove: (type, fn)=>{if(_messages[type] && _messages[type] instanceof Array){for(let i = _messages[type].length - 1; i >= 0; i--){_messages[type][i] == fn && _messages[type].splice(i, 1);
}
}
}
组合起来,一个残缺的观察者对象就是上面这样:
const Observer = (function(){var _messages = {};
return {add: (type, fn)=>{if(_messages[type]){_messages[type].push(fn)
}else{_messages[type] = [fn]
}
},
emit: (type, args)=>{if(_messages[type] && _messages[type] instanceof Array){for(let i = 0; i < _messages[type].length; i++){typeof _messages[type][i] == 'function' && _messages[type][i].call(this, args)
}
}
},
remove: (type, fn)=>{if(_messages[type] && _messages[type] instanceof Array){for(let i = _messages[type].length - 1; i >= 0; i--){_messages[type][i] == fn && _messages[type].splice(i, 1);
}
}
}
}
})()
接下来,让咱们看看它是怎么工作的,还以学生答问题为例,咱们先来一个老师,老师就是公布发问:
var Teacher = function(){this.question = function(questionName){console.log('老师发问了:' + questionName);
Observer.emit(questionName, {question: questionName})
}
}
而后是学生,学生有一个答复问题的办法,有一个监听问题的办法,只监听本人会的问题,另外还有一个睡觉办法,睡觉其实就是勾销订阅某个音讯。
var Student = function(name){
this.name = name;
this.answer = function(args){console.log(name +'答复老师的问题:' + args.question);
}
}
Student.prototype = {listen: function(questionName){console.log(this.name +'想要答复问题:' + questionName);
Observer.add(questionName, this.answer)
},
sleep: function(questionName){console.log(this.name +'睡着了');
Observer.remove(questionName, this.answer)
}
}
listen 和 sleep 能够放到原型链上,然而 answer 办法放在实例上了,因为如果再放在原型链上,观察者就没法分清哪个回调是哪个订阅者的了。
有了以上两个对象,咱们联合上面的代码,看看观察者怎么工作:
var s1 = new Student('小明');
var s2 = new Student('小红');
s1.listen("谁最帅");
s2.listen("谁最帅");
var t = new Teacher();
setTimeout(() => {t.question('谁最帅');
s1.sleep("谁最帅");
setTimeout(() => {t.question('谁最帅')
}, 2000);
}, 2000);
咱们创立两个学生,别离监听了 ’ 谁最帅 ’ 这个问题,而后创立一个老师,老师两秒后发问 ’ 谁最帅 ’,两个学生应该都答复该问题。而后学生 s1 睡着了,于是不再监听 ’ 谁最帅 ’,老师 2 秒后又发问,这次只有学生 s2 答复该问题。
残缺的后果是上面这样的。
小明想要答复问题:谁最帅
小红想要答复问题:谁最帅
老师发问了:谁最帅
小明答复老师的问题:谁最帅
小红答复老师的问题:谁最帅
小明睡着了
老师发问了:谁最帅
小红答复老师的问题:谁最帅
会呈现下面的后果就是观察者在起作用,两个学生监听发问,Observer 往_messages 中插入了 ’ 谁最帅 ’,而且把两位学生的答复办法放进了数组里,等老师发问,Observer 就去_messages 中顺次执行。起初 s1 勾销了订阅,Observer 从_messages 删除了该对象对事件的订阅,从而第二次发问的时候,s1 就不再答复了。