观察者模式
什么观察者模式
定义对象间的一对多依赖关系,当一个对象(被观察者)状态发生改变时,所有依赖于它的对象(观察者)都会自动收到通知并更新。
一、现实类比 🌰
📰 报纸订阅:
- 你(观察者)向报社(被观察者/主题)订阅报纸。
- 只要报社发布新一期报纸(状态变化),就会自动送报到你家(通知你)。
- 其他人也可以订阅,报社不需要知道具体是谁在看,只需“广播”即可。
- 你可以随时取消订阅(解耦)。
✅ 这就是典型的“一对多”、“自动通知”、“松耦合”。
二、核心角色
| 角色 | 职责 |
|---|---|
| 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');
观察者模式与发布-订阅模式的区别?
| 特性 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 被观察者持有观察者引用(较紧) | 通过事件总线/中间件解耦(更松) |
| 同步/异步 | 通常是同步通知 | 通常是异步(如消息队列) |
| 实现复杂度 | 简单 | 需要额外的调度中心 |
💡 发布-订阅是观察者模式的增强版,适用于跨系统、异步场景。