你是否曾在项目中遇到过这样的场景:A一变,B、C、D都要跟着变?这时,设计模式中的“观察者模式”和“发布订阅模式”就像两位武林高手,纷纷出场。今天,我们就用最通俗的语言、最有趣的比喻,带你彻底搞懂这对“孪生兄弟”!
一、概念速览:谁是谁?
观察者模式(Observer Pattern)
"你盯着我,我一动你就跟着动。"
- 定义:对象间一对多依赖,被观察者状态变化时,自动通知所有观察者。
- 特点:被观察者直接持有观察者引用,紧耦合。
- 生活类比:老师点名,学生立刻答到。
发布订阅模式(Publish-Subscribe Pattern)
"你别盯着我,咱们都找小黑板(事件中心)留言,有事小黑板通知大家。"
- 定义:通过事件中心解耦发布者和订阅者,三者互不认识。
- 特点:松耦合,灵活扩展。
- 生活类比:小区公告栏,物业贴通知,业主自取。
二、代码实战:用代码说话
1. 观察者模式
经典实现:属性监听与DOM联动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>观察者模式示例</title>
</head>
<body>
<h2>1</h2>
<button>点击</button>
<script>
let btn = document.querySelector('button');
let h2 = document.querySelector('h2');
let obj = { count: 1 };
let num = obj.count; // 防止递归爆栈
function observer(value) {
// 找到订阅者(h2),更新内容
h2.innerHTML = value;
}
Object.defineProperty(obj, 'count', {
get() { return num; },
set(value) {
observer(value); // 通知观察者
num = value;
}
});
btn.addEventListener('click', () => {
obj.count++;
console.log('当前count:', obj.count);
});
</script>
</body>
</html>
- 解读:属性变化时,直接调用观察者函数,更新DOM。
- 优点:简单直接,适合小范围、强关联场景。
- 输出说明 :每点击一次按钮,h2内容和控制台同步更新。
2. 发布订阅模式
完整事件中心实现
class EventEmitter {
constructor() {
this.eventList = {};
}
on(eventName, callback) {
if (!this.eventList[eventName]) {
this.eventList[eventName] = [];
}
this.eventList[eventName].push(callback);
}
off(eventName, callback) {
const callbacks = this.eventList[eventName];
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
}
once(eventName, callback) {
const wrap = () => {
callback();
this.off(eventName, wrap);
};
this.on(eventName, wrap);
}
emit(eventName) {
if (this.eventList[eventName]) {
const handlers = this.eventList[eventName].slice();
handlers.forEach(item => item());
}
}
}
function kang() { console.log('康买房'); }
function ji() { console.log('季买房'); }
function chen() { console.log('陈买车位'); }
let _event = new EventEmitter();
_event.on('hasHouse', kang);
_event.once('hasHouse', ji);
_event.on('hasCarport', chen);
_event.emit('hasHouse'); // 输出:康买房\n季买房
_event.emit('hasHouse'); // 输出:康买房
_event.emit('hasCarport'); // 输出:陈买车位
- 解读:事件中心统一管理,发布和订阅互不认识。
- 优点:解耦,适合复杂、跨模块通信。
- 输出说明 :
- 第一次emit('hasHouse'),康买房、季买房都被调用。
- 第二次emit('hasHouse'),只有康买房被调用(once只执行一次)。
- emit('hasCarport'),陈买车位被调用。
三、核心对比:一表胜千言
| 特性 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 耦合度 | 紧耦合 | 松耦合 |
| 通信方式 | 直接通知 | 事件中心转发 |
| 角色数量 | 两个(被观察者、观察者) | 三个(发布者、订阅者、事件中心) |
| 适用场景 | UI响应、数据绑定 | 跨模块、全局事件管理 |
| 代码复杂度 | 简单 | 略高 |
四、幽默总结:
- 观察者模式像“班主任和学生”,老师一喊,学生立刻响应。
- 发布订阅模式像“微信群公告”,群主发消息,谁看谁响应,群主不用知道谁看了。
五、应用场景举例
- 观察者模式:Vue响应式、Excel单元格联动
- 发布订阅模式:Node.js EventEmitter、浏览器自定义事件
六、彩蛋:代码小实验
4.js中的Object.defineProperty妙用
const p = { a: 1 };
let aa = p.a;
Object.defineProperty(p, 'a', {
get() { return aa; },
set(value) { aa = value; console.log('set', value); }
});
p.a = 2;
- 解读:用getter/setter实现属性监听,和观察者思想如出一辙!
七、结语
无论你是“老师点名”派,还是“公告栏通知”派,理解这两种模式的本质,才能在实际开发中游刃有余。下次遇到“消息通知”需求时,别忘了让这两位高手出场哦!😄