前高频端面试题合集23-观察者与发布订阅者模式

1. 观察者模式

官方给出的观察者模式的解释是这样的:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式实现的,其实就是当目标对象的某个属性发生了改变,所有依赖着目标对象的观察者都将接到通知,做出相应动作。 所以在目标对象的抽象类里,会保存一个观察者序列。当目标对象的属性发生改变生,会从观察者队列里取观察者调用各自的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Subject {
constructor() {
this.observers = [];
}

add(observer) {
this.observers.push(observer);
}

notify(...args) {
this.observers.forEach(observer => observer.update(...args));
}
}

class Observer {
update(...args) {
console.log(...args);
}
}

// 创建观察者ob1
let ob1 =new Observer();
// 创建观察者ob2
let ob2 =new Observer();
// 创建目标sub
let sub =new Subject();
// 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
sub.add(ob1);
// 目标sub添加观察者ob2
sub.add(ob2);
// 目标sub触发SMS事件(目标主动通知观察者)
sub.notify('I fired `SMS` event');

2. 发布/订阅模式

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

发布/订阅模式相比于观察者模式多了一个中间媒介,因为这个中间媒介,发布者和订阅者的关联更为松耦合

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

也就是说,发布/订阅模式和观察者最大的差别就在于消息是否通过一个中间类进行转发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
classPubSub {
constructor() {
this.subscribers = [];
}

subscribe(topic, callback) {
letcallbacks =this.subscribers[topic];
if(!callbacks) {
this.subscribers[topic] = [callback];
}else{
callbacks.push(callback);
}
}

publish(topic, ...args) {
letcallbacks =this.subscribers[topic] || [];
callbacks.forEach(callback => callback(...args));
}
}

// 创建事件调度中心,为订阅者和发布者提供调度服务
letpubSub =newPubSub();
// A订阅了SMS事件(A只关注SMS本身,而不关心谁发布这个事件)
pubSub.subscribe('SMS', console.log);
// B订阅了SMS事件
pubSub.subscribe('SMS', console.log);
// C发布了SMS事件(C只关注SMS本身,不关心谁订阅了这个事件)
pubSub.publish('SMS','I published `SMS` event');

3. 两种模式的区别

由上,我们就可以得出这两者的区别了:

  • 观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

4. 两种模式举例

观察者:事件处理(一旦触发事件,立即执行)

1
2
3
document.body.addEventListener('click',function(){
alert('我也是一个观察者,你一点击,我就知道了');
});

  其中,body是发布者,即目标对象,当被点击的时候,向观察者反馈这一事件;JavaScript中函数也是一个对象,click这个事件的处理函数(alert(‘…’))就是观察者,当接收到目标对象反馈来的信息时进行一定处理。

发布订阅者:事件循环 (例如 发布者:ajax请求,发出去返回之后存在消息队列中,订阅者主线程并不会立即处理回调,而是等着事件循环机制在中间层将回调函数推送导致主线程中执行)