JS `Observer` 模式与 `Event Emitter` 实现:解耦与事件驱动

各位靓仔靓女,晚上好!我是今晚的讲师,很高兴能和大家一起聊聊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不再收到通知)

代码解析:

  1. WeatherStation 类是主题,负责维护观察者列表 (observers),并提供 subscribeunsubscribesetTemperature 方法。
  2. User 类是观察者,实现了 update 方法,用于接收主题发来的通知。
  3. 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分钟"

代码解析:

  1. AlarmClock 类继承了 EventEmitter,从而拥有了事件发射和监听的能力。
  2. setAlarm 方法用于设置闹钟时间,当时间到达时,会调用 this.emit('ring', '起床啦!') 发出 'ring' 事件,并传递消息。
  3. 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 事件(如 clickmouseover)都是基于 Event Emitter 模式实现的。

六、最后的小贴士

  • 不要滥用 ObserverEvent Emitter 模式,过度使用可能会导致代码难以理解和维护。
  • 在选择使用哪种模式时,要根据具体的应用场景进行权衡。
  • 可以结合使用这两种模式,以实现更灵活和强大的解耦效果。

好了,今天的分享就到这里。希望大家能够掌握 Observer 模式和 Event Emitter 模式,并在实际开发中灵活运用,写出更优雅、更健壮的代码!如果大家还有什么疑问,欢迎随时提问! 散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注