各位靓仔靓女,晚上好!我是今晚的讲师,很高兴能和大家一起聊聊JavaScript里的两种好东西:Observer
模式和Event Emitter
。它们就像代码界的“解耦神器”,能让你的代码更灵活、更易维护,而且还能实现酷炫的事件驱动编程。
今天我们就来扒一扒它们的皮,看看它们到底是怎么做到这些的,以及怎么用它们来让我们的代码更上一层楼。
一、开胃小菜:为啥我们需要解耦?
想象一下,你开了一家餐厅,厨房(后端)负责做菜,服务员(前端)负责把菜端给顾客。如果服务员直接跑进厨房盯着厨师做菜,那画面太美我不敢看!这叫高耦合,耦合度太高意味着:
- 改动困难: 厨房稍微改动一下菜谱,服务员的端菜方式就得跟着改,一动全身。
- 复用性差: 如果想让服务员去隔壁餐厅兼职,发现他们那边的厨房流程不一样,直接懵逼。
- 维护困难: 出了问题,得把厨房和服务员一起检查,效率低下。
解耦就是要把厨房和服务员之间的直接依赖关系去掉,让他们通过某种“消息机制”来通信。厨师做好菜,发出一个“菜已上桌”的消息,服务员收到消息后,再去端菜。这样,厨房和服务员就独立了,互不影响,这就是解耦的好处。
二、正餐一:Observer
模式——默默关注你的“动向”
Observer
模式,又叫发布-订阅(Publish-Subscribe)模式,就像你订阅了一个公众号,公众号发布了新文章,你就会收到通知。 在代码世界里,它包含两个主要角色:
- Subject(主题): 相当于公众号,维护着一个观察者列表,负责发布状态变化的消息。
- Observer(观察者): 相当于订阅者,当主题的状态发生变化时,会收到通知并执行相应的操作。
代码示例:一个简单的天气预报系统
// Subject(主题):天气预报中心
class WeatherStation {
constructor() {
this.observers = []; // 观察者列表
this.temperature = 25; // 当前温度
}
// 添加观察者
subscribe(observer) {
this.observers.push(observer);
}
// 移除观察者
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// 设置温度,并通知观察者
setTemperature(temperature) {
this.temperature = temperature;
this.notifyObservers();
}
// 通知所有观察者
notifyObservers() {
this.observers.forEach(observer => {
observer.update(this.temperature);
});
}
}
// Observer(观察者):用户
class User {
constructor(name) {
this.name = name;
}
// 收到温度更新的通知
update(temperature) {
console.log(`${this.name}: Current temperature is ${temperature}°C`);
}
}
// 创建天气预报中心
const weatherStation = new WeatherStation();
// 创建用户
const user1 = new User("Alice");
const user2 = new User("Bob");
// 订阅天气预报
weatherStation.subscribe(user1);
weatherStation.subscribe(user2);
// 天气变化,更新温度
weatherStation.setTemperature(30); // Alice: Current temperature is 30°C, Bob: Current temperature is 30°C
weatherStation.setTemperature(20); // Alice: Current temperature is 20°C, Bob: Current temperature is 20°C
// 取消订阅
weatherStation.unsubscribe(user1);
// 再次更新温度
weatherStation.setTemperature(35); // Bob: Current temperature is 35°C (Alice不再收到通知)
代码解析:
WeatherStation
类是主题,负责维护观察者列表 (observers
),并提供subscribe
、unsubscribe
和setTemperature
方法。User
类是观察者,实现了update
方法,用于接收主题发来的通知。- 当
WeatherStation
的温度发生变化时,会调用notifyObservers
方法,遍历所有观察者,并调用它们的update
方法,从而实现通知机制。
Observer
模式的优点:
- 解耦: 主题和观察者之间解耦,主题不需要知道观察者的具体实现,只需要知道它们实现了
update
方法即可。 - 灵活性: 可以动态地添加和移除观察者,而不会影响主题的代码。
- 可扩展性: 可以很容易地添加新的观察者,而不需要修改主题的代码。
三、硬菜二:Event Emitter
——广播你的“声音”
Event Emitter
模式,又叫事件发布-订阅模式,它允许对象(称为“发射器”)发出事件,而其他对象(称为“监听器”)可以监听这些事件并执行相应的操作。 就像电台广播,电台(发射器)播放音乐,听众(监听器)可以收听并享受音乐。
代码示例:一个简单的闹钟系统
// 引入 events 模块
const EventEmitter = require('events');
// 创建一个闹钟类,继承 EventEmitter
class AlarmClock extends EventEmitter {
constructor() {
super();
}
// 设置闹钟时间
setAlarm(time) {
setTimeout(() => {
this.emit('ring', '起床啦!'); // 发出 'ring' 事件,并传递消息
}, time);
}
}
// 创建一个闹钟实例
const alarmClock = new AlarmClock();
// 监听 'ring' 事件
alarmClock.on('ring', (message) => {
console.log(`闹钟响了:${message}`);
});
// 设置闹钟,3秒后响铃
alarmClock.setAlarm(3000); // 3秒后输出 "闹钟响了:起床啦!"
// 可以添加多个监听器
alarmClock.on('ring', (message) => {
console.log(`(懒虫模式)闹钟响了:${message} 再睡5分钟`);
});
// 设置闹钟,6秒后响铃
alarmClock.setAlarm(6000); // 6秒后输出 "闹钟响了:起床啦!" 和 "(懒虫模式)闹钟响了:起床啦!再睡5分钟"
代码解析:
AlarmClock
类继承了EventEmitter
,从而拥有了事件发射和监听的能力。setAlarm
方法用于设置闹钟时间,当时间到达时,会调用this.emit('ring', '起床啦!')
发出'ring'
事件,并传递消息。alarmClock.on('ring', (message) => { ... })
用于监听'ring'
事件,当事件发生时,会执行回调函数。
Event Emitter
模式的优点:
- 解耦: 发射器和监听器之间解耦,发射器不需要知道监听器的具体实现,只需要知道要发出什么事件即可。
- 灵活性: 可以动态地添加和移除监听器,而不会影响发射器的代码。
- 可扩展性: 可以很容易地添加新的事件和监听器,而不需要修改发射器的代码。
- 异步处理: 非常适合处理异步事件,比如用户点击、网络请求等。
四、饭后甜点:Observer
vs Event Emitter
,傻傻分不清?
很多人容易把 Observer
模式和 Event Emitter
模式混淆,它们看起来很像,但其实有一些关键的区别:
特性 | Observer 模式 |
Event Emitter 模式 |
---|---|---|
角色 | Subject (主题) 和 Observer (观察者) |
Emitter (发射器) 和 Listener (监听器) |
关系 | 观察者知道主题的存在,需要显式订阅。 | 监听器不需要知道发射器的存在,通过事件名称订阅。 |
耦合度 | 较高,观察者需要实现主题定义的接口。 | 较低,发射器和监听器之间完全解耦。 |
应用场景 | 当对象的状态变化需要通知其他对象时,且这些对象需要知道状态变化的上下文。 | 当对象需要广播事件,而不需要知道谁在监听时。 |
典型应用 | 数据绑定、状态管理。 | 用户界面事件处理、网络请求、消息队列。 |
事件名称 | 通常没有明确的事件名称,而是通过 update 方法传递状态变化。 |
必须有明确的事件名称,用于区分不同的事件。 |
消息传递 | 通常传递状态变化的上下文信息。 | 可以传递任意数据作为事件的参数。 |
总结:
Observer
模式更适合于对象之间存在直接依赖关系,且观察者需要知道状态变化的上下文的情况。Event Emitter
模式更适合于对象之间需要完全解耦,且只需要广播事件的情况。
简单粗暴的理解:
Observer
模式就像是公司内部的通知,你知道是谁发的,你也知道通知的内容是什么。Event Emitter
模式就像是广播,你不知道是谁在广播,你只知道广播的内容是什么。
五、加餐:实际应用场景
- React/Vue 的状态管理: Redux 和 Vuex 都使用了
Observer
模式来实现状态的更新和通知。 - Node.js 的事件循环: Node.js 的事件循环是基于
Event Emitter
模式实现的,用于处理异步事件。 - 前端框架的事件处理: 浏览器中的 DOM 事件(如
click
、mouseover
)都是基于Event Emitter
模式实现的。
六、最后的小贴士
- 不要滥用
Observer
和Event Emitter
模式,过度使用可能会导致代码难以理解和维护。 - 在选择使用哪种模式时,要根据具体的应用场景进行权衡。
- 可以结合使用这两种模式,以实现更灵活和强大的解耦效果。
好了,今天的分享就到这里。希望大家能够掌握 Observer
模式和 Event Emitter
模式,并在实际开发中灵活运用,写出更优雅、更健壮的代码!如果大家还有什么疑问,欢迎随时提问! 散会!