有這么一段代碼經(jīng)常會出現(xiàn)在代碼中
var pubsub = (()=>{
var topics = {};
function subscribe(topic,fn){
if(!topics[topic]){
topics[topic] = [];
}
topics[topic].push(fn);
}
function publish(topic,...args){
if(!topics[topic])
return;
for(let fn of topics[topic]){
fn(...args);
}
}
return {
subscribe,
publish
}
})()
測試代碼
pubsub.subscribe('test',function(a,b){ //訂閱者A訂閱了test事件
console.log(a,b);
});
pubsub.publish('test','123','HH'); //123 HH(發(fā)布者B發(fā)布了test事件)
調(diào)用publish后打印出了123 HH。很奇妙的一段代碼,當(dāng)然實際上只是遍歷了數(shù)組,然后把數(shù)組中的所有函數(shù)全部執(zhí)行一遍而已。但是對于一個沒讀過實現(xiàn)代碼的人來說,卻是一個神奇的存在,JS居然能訂閱發(fā)布消息,太酷了。
有的時候我會叫他觀察者模式,有時候又會叫他發(fā)布訂閱模式,覺得叫什么都是對的。
但是,他們并不一樣。
差異
在觀察者模式中,觀察者需要直接訂閱目標(biāo)事件。在目標(biāo)發(fā)出內(nèi)容改變的事件后,直接接收事件并作出響應(yīng)。發(fā)布訂閱模式相比觀察者模式多了個事件通道,訂閱者和發(fā)布者不是直接關(guān)聯(lián)的。
這段話可以看出上面的例子是發(fā)布訂閱模式。訂閱者A和發(fā)布者B是通過pubsub這個對象關(guān)聯(lián)起來的,他們沒有直接的交流。
那么真正的觀察者模式是怎么樣的?
一個或多個觀察者對目標(biāo)的狀態(tài)感興趣,通過將自己依附在目標(biāo)對象上以便注冊所感興趣的內(nèi)容。目標(biāo)狀態(tài)發(fā)生改變并且觀察者可能對這些改變感興趣,會發(fā)送一個通知消息,調(diào)用每個觀察者的更新方法。當(dāng)觀察者不再對目標(biāo)狀態(tài)感興趣時,他們可以簡單將自己從中分離。
我們來實現(xiàn)下觀察者模式。首先是目標(biāo)的構(gòu)造函數(shù),他有個數(shù)組,用于添加觀察者。還有個廣播方法,遍歷觀察者數(shù)組后調(diào)用他們的update方法:
class Subject{
constructor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
notify(){
this.subs.forEach(sub=> {
sub.update();
});
}
}
那么觀察者就得有個update方法:
class Observer{
update(){
console.log('update');
}
}
測試代碼
let subject = new Subject();
let ob = new Observer();
//目標(biāo)添加觀察者了
subject.addSub(ob);
//目標(biāo)發(fā)布消息調(diào)用觀察者的更新方法了
subject.notify(); //update
可以看到目標(biāo)和觀察者是直接聯(lián)系在一起的。觀察者把自身添加到了目標(biāo)對象中,可見和發(fā)布訂閱模式差別還是很大的。在這種模式下,目標(biāo)更像一個發(fā)布者,他讓添加進來的所有觀察者都執(zhí)行了update函數(shù),而觀察者就像一個訂閱者。
優(yōu)劣
個人覺得發(fā)布/訂閱模式比較簡單,使用的也比較廣泛。由于他訂閱者和發(fā)布者不直接關(guān)聯(lián)的特點我們完全可以把管理事件的對象寫到一個單獨文件中,作為庫來使用。發(fā)布訂閱模式中,雙方不知道對方的存在,而觀察者模式中,目標(biāo)和觀察者是直接聯(lián)系起來的。具體選擇什么模式,得視場景而定。一般來說,發(fā)布/訂閱就夠用了,簡單清晰,符合我們dom事件編程的觀念。
觀察者模式哪里被用到過?vue的雙向綁定。下篇就講一下觀察者模式在vue雙向綁定實現(xiàn)中的運用。