跳到主要内容

观察者模式

什么观察者模式

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

一、现实类比 🌰

📰 报纸订阅:

  • 你(观察者)向报社(被观察者/主题)订阅报纸。
  • 只要报社发布新一期报纸(状态变化),就会自动送报到你家(通知你)。
  • 其他人也可以订阅,报社不需要知道具体是谁在看,只需“广播”即可。
  • 你可以随时取消订阅(解耦)。

✅ 这就是典型的“一对多”、“自动通知”、“松耦合”。


二、核心角色

角色职责
Subject(被观察者 / 主题)管理观察者列表,提供注册(attach)、移除(detach)、通知(notify)方法
Observer(观察者)定义一个更新接口(如 update()),供被观察者调用
ConcreteSubject(具体被观察者)存储状态,当状态变化时调用 notify()
ConcreteObserver(具体观察者)实现 update(),根据主题状态更新自身

三、代码实现

let publisher = {
subscribers: {
any: []
},

// 将订阅者添加到 subscribers 中对应的数组中
subscribe: function (fn, type = 'any') {
if (typeof this.subscribers[type] === 'undefined') {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
},

// 在 subscribers 中删除订阅者
unsubscribe: function (fn, type) {
this.visitSubscribers('unsubscribe', fn, type);
},

// 循环遍历 subscribers 中的每个元素,并调用他们注册时提供的方法
publish: function (publication, type) {
this.visitSubscribers('publish', publication, type);
},

// 在 publish 调用
visitSubscribers: function (action, arg, type = 'any') {
this.subscribers[type].forEach((currentValue, index, array) => {
if (action === 'publish') {
// 函数调用
currentValue(arg);
} else if (action === 'unsubscribe') {
if (currentValue === arg) {
this.subscribers[type].splice(index, 1);
}
}
});
}
};

let funcA = function (cl) {
console.log('msg1' + cl);
};

let funcB = function (cl) {
console.log('msg2' + cl);
};

// 添加 订阅者 funcA
publisher.subscribe(funcA);
// 添加 订阅者 funcB
publisher.subscribe(funcB);
// 删除 订阅者 funcB
publisher.unsubscribe(funcB);

publisher.publish(' in publisher'); // msg1 in publisher

四、发布-订阅模式

var pubsub = {};
(function (q) {
// 回调函数存放的数组
var topics = {},
subUid = -1;
// 发布或广播事件,包含特定的 topic 名称和参数(比如传递的数据)
q.publish = function (topic, args) {
if (!topics[topic]) {
return false;
}
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
return this;
};
// 通过特定的名称和回调函数订阅事件,topic/event 触发时执行事件
q.subscribe = function (topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
// 基于订阅上的标记引用,通过特定 topic 取消订阅
q.unsubscribe = function (token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return this;
};
})(pubsub);

// 简单的消息记录器记承所有通过订者接收到的主题(topie)和数器
var messagelogger = function (topics, data) {
console.log('Logging' + topics + ' => ' + JSON.stringify(data));
};
var topicName = 'inbox/newMessage';
//订阅者监听订阅的 topic;一旦该topc广播一个通知,订着数调用回调函数
var subscription = pubsub.subscribe(topicName, messagelogger);
// 发布者负责发布程序感兴趣的 topic或通知,例如:
pubsub.publish(topicName, 'hello world!');
// 或者
pubsub.publish(topicName, ['test', 'a', 'b', 'c']);
// 或者
pubsub.publish(topicName, { sender: 'hello@google.com', body: 'Hey again!' });
// 如果订阅者不想被通知了,也可以取消订阅
pubsub.unsubscribe('0');
//一旦取消订阅,下面的代码执行后将不会记录消息,因为订阅者不再进行监听了
pubsub.publish(topicName, 'Hello! are you still there?');

手写一个发布-订阅模式

  • on():负责注册事件的监听器,指定事件触发时的回调函数。
  • emit():负责触发事件,可以通过传参使其在触发的时候携带数据 。
  • off():负责监听器的删除。
class myEventEmitter {
constructor() {
this.eventMap = {};
}
on(type, handler) {
if (!(handler instanceof Function)) {
throw new Error('请传一个函数');
}
if (!this.eventMap[type]) {
this.eventMap[type] = [];
}
this.eventMap[type].push(handler);
}
emit(type, params) {
if (this.eventMap[type]) {
this.eventMap[type].forEach((handler, index) => {
handler(params);
});
}
}
off(type, handler) {
if (this.eventMap[type]) {
this.eventMap[type].splice(this.eventMap[type].indexOf(handler) >>> 0, 1);
}
}
}

const myEvent = new myEventEmitter();

const testHandler = function (params) {
console.log(`test事件被触发了,testHandler 接收到的入参是${params}`);
};

// 监听 test 事件
myEvent.on('test', testHandler);

// 在触发 test 事件的同时,传入希望 testHandler 感知的参数
myEvent.emit('test', 'newState');

观察者模式与发布-订阅模式的区别?

特性观察者模式发布-订阅模式
耦合度被观察者持有观察者引用(较紧)通过事件总线/中间件解耦(更松)
同步/异步通常是同步通知通常是异步(如消息队列)
实现复杂度简单需要额外的调度中心

💡 发布-订阅是观察者模式的增强版,适用于跨系统、异步场景。