当前位置: 首页 > article >正文

从0到1手写实现Event Emitter

1. 引言

在前端开发中,尤其是构建大型应用时,组件之间的通信变得非常复杂。为了实现组件之间的解耦,我们通常会采用事件驱动的方式。事件中心(Event Emitter)机制就是通过集中管理和分发事件来解耦生产者(事件的触发者)和消费者(事件的处理者)。这种机制尤其适合在复杂的前端应用中,减少组件之间的直接依赖。

本文将介绍如何从0到1实现一个简单的事件中心,并用 JavaScript 来构建,适合初学者理解。我们将一步步实现事件中心,并展示如何在实际的前端应用中使用它。

2. 事件中心的基本概念

2.1 事件中心的工作原理

事件中心的核心功能是“订阅”和“发布”事件。生产者通过事件中心发布事件,消费者则通过事件中心订阅感兴趣的事件。事件中心接收到事件时,会将其分发给相应的消费者。

  • 发布事件 :当某个组件发生了某种操作时,它会向事件中心发布一个事件。

  • 订阅事件 :其他组件可以订阅特定类型的事件,当事件发生时,事件中心会通知这些组件。

2.2 事件中心的优势

  • 解耦 :生产者不需要知道谁会处理事件,消费者也不需要知道事件是如何触发的。

  • 提高扩展性 :如果需要增加新的功能或模块,只需要订阅或发布事件,不需要修改现有代码。

  • 提高响应性 :事件可以是异步的,消费者可以在事件到达时进行异步处理,避免了同步执行可能带来的性能问题。

3. 手写实现事件中心

接下来,我们将用 JavaScript 从头开始实现一个简单的事件中心,支持事件的订阅和发布。

3.1 设计事件中心类

我们将首先设计一个 EventEmitter 类,这个类需要提供两个主要的功能:

  • on(eventName, callback):用于订阅事件。

  • emit(eventName, ...args):用于发布事件。

  • off(eventName, ...args):用于取消订阅事件。

我们可以使用 JavaScript 中的对象({})来存储事件类型和对应的回调函数队列。

3.2 代码实现

class EventEmitter {
    constructor () {
        this.events = {}
    }
    // 订阅事件
    on (eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = []
        }
        this.events[eventName].push(callback)
        console.log(`Subscribed to event: ${eventName}`);
    }
    // 发布事件
    emit (eventName, ...args) {
        if (!this.events[eventName]) {
            return
        }
        this.events[eventName].map(callback => callback(...args))
        console.log(`Event emit: ${eventName}`);
    }
    // 取消订阅事件
    off (eventName, callback) {
        const eventCallbacks = this.events[eventName]
		if (!eventCallbacks) return
        this.events[eventName] = eventCallbacks.filter(cb => cb !== callback)
        console.log(`off event: ${eventName}`);
    } 
}

3.3 事件中心的使用

下面我们来看一下如何在实际的前端应用中使用这个事件中心。

// 创建一个事件中心实例
const eventEmitter = new EventEmitter();

// 定义事件的消费者回调
function onUserLogin(data) {
  console.log(`User logged in: ${data.username}`);
}

function onUserLogout(data) {
  console.log(`User logged out: ${data.username}`);
}

// 订阅事件
eventEmitter.on('userLogin', onUserLogin);
eventEmitter.on('userLogout', onUserLogout);

// 发布事件
eventEmitter.emit('userLogin', { username: 'kobe' });
eventEmitter.emit('userLogout', { username: 'jordan' });

// 取消订阅
// eventEmitter.off('userLogin', onUserLogin);
eventEmitter.emit('userLogin', { username: 'kobe' }); 


 
我们可以看到在注释掉off事件,可以看到kobe的logged in的信息

// 创建一个事件中心实例
const eventEmitter = new EventEmitter();

// 定义事件的消费者回调
function onUserLogin(data) {
  console.log(`User logged in: ${data.username}`);
}

function onUserLogout(data) {
  console.log(`User logged out: ${data.username}`);
}

// 订阅事件
eventEmitter.on('userLogin', onUserLogin);
eventEmitter.on('userLogout', onUserLogout);

// 发布事件
eventEmitter.emit('userLogin', { username: 'kobe' });
eventEmitter.emit('userLogout', { username: 'jordan' });

// 取消订阅
eventEmitter.off('userLogin', onUserLogin);
eventEmitter.emit('userLogin', { username: 'kobe' });  // 不会触发回调

 

但是当我们正常触发off事件时,就看不到kobe的logged in的信息
 

3.4 解释代码

  • on方法 :用于订阅某个事件。当某个组件对某个事件感兴趣时,它会调用 on方法,传入事件类型和回调函数。事件中心将这个回调函数保存在一个数组中,当事件发布时,会依次执行这些回调函数。

  • emit方法 :用于发布事件。发布者不需要知道谁会处理事件,它只需要向事件中心发布一个事件,并附带数据。事件中心会遍历所有订阅了该事件类型的回调函数并执行它们。

  • off方法 :如果某个组件不再关心某个事件,可以调用 off 方法,取消对该事件的订阅。

4. 事件中心的应用场景

4.1 用户登录/登出事件

在一个大型的前端应用中,用户登录和登出是非常常见的场景。当用户登录或登出时,可能需要执行一些操作(如更新UI、获取用户信息、重定向页面等)。通过事件中心,我们可以将这些操作解耦,避免紧耦合的逻辑。

例如,当用户登录时,我们可以触发 userLogin 事件,其他组件(如用户信息模块、UI模块等)可以订阅这个事件,并在事件触发时执行相应的操作。

4.2 表单提交

在一个表单提交的场景中,用户输入数据并提交表单时,可能会触发多个操作(如数据验证、请求发送、显示加载状态等)。通过事件中心,我们可以将这些操作分开处理,避免在同一个地方处理所有逻辑,简化代码。

4.3 组件间通信

在前端开发中,尤其是单页面应用(SPA)中,多个组件之间经常需要进行通信。通过事件中心机制,组件之间可以通过事件来交换数据,而不需要直接调用其他组件的方法或修改它们的状态。

5. 优化与扩展

5.1 异步处理

我们可以通过异步方式来处理事件,使得事件的消费更加高效。比如可以使用 setTimeoutPromise 来异步执行回调函数,避免阻塞主线程。

5.2 支持一次性订阅

我们可以添加一个一次性订阅功能,使得某些事件只会被触发一次后自动取消订阅。这在某些场景下会非常有用。

once (eventName, callback) {
  const onceCallback = (...args) => {
    callback(...args)
	// 这块注意是onceCallback,而不是callback
    this.off(eventName, onceCallback)
    // console.log(`once event: ${eventName}`);
  }
  this.on(eventName, onceCallback)
}

// 调用一下
eventEmitter.once('userLogin', onUserLogin); 
eventEmitter.emit('userLogin', { username: 'durant' });
eventEmitter.emit('userLogin', { username: 'durant' });


 
可以看到user logged in: durant只有一次调用

6. 总结

本文通过从0到1的方式实现了一个简单的事件中心,并讲解了如何在前端应用中使用它来解耦组件之间的通信。事件中心可以帮助我们处理异步事件、提高系统扩展性并简化代码结构,尤其适合用于大型前端应用中的组件间通信。通过本文的代码实现,相信你已经能掌握事件中心的基本原理和使用方法,并能在实际项目中加以应用。


http://www.kler.cn/a/449277.html

相关文章:

  • 由于这些关键原因,我总是手边有一台虚拟机
  • 2、光同步数字传送网的特点
  • aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
  • MacOS 命令行详解使用教程
  • 【机器学习与数据挖掘实战】案例04:基于K-Means算法的信用卡高风险客户识别
  • 通义千问对接FreeSWITCH实现大模型呼叫中心
  • 关于Buildroot如何配置qtwebengine [未能成功编译]
  • 面试题整理15----K8s常见的网络插件有哪些
  • 对于其他管理的理解(中)
  • 【Flink-scala】DataSet编程模型介绍及数据源
  • Pytorch | 从零构建ParNet/Non-Deep Networks对CIFAR10进行分类
  • 在FreeRTOS中动态创建任务,假如在最后一个参数写NULL,该任务有任务句柄吗
  • 安装管理docker
  • 重温设计模式--享元模式
  • 路由器做WPAD、VPN、透明代理中之间一个
  • CSS系列(24)-- 打印样式详解
  • 基于JAVA_JSP电子书下载系统的设计与实现【源码+文档+部署讲解】
  • 设计模式详解(十二):单例模式——Singleton
  • 如何注册和使用Facebook企业号
  • uniapp验证码
  • 数据库管理-第274期 Oracle Enterprise Manager 24ai新特性一览(20241223)
  • 使用frp进行内网穿透
  • 程控电阻箱应用中需要注意哪些安全事项?
  • Log4j简介
  • 在Excel中绘制ActiveX控件:解决文本编辑框定位问题
  • ubuntu装P104