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

### Vue3 微前端架构实现教程 #### 1. 初始化项目结构 为了创建一个基于 Vue3 的微前端架构,首先需要初始化多个独立的应用程序作为子模块。每个应用程序都可以单独开发、测试和部署。 ```bash npx create-vite@latest subapp-one --template vue cd subapp-one npm install ``` 重复上述命令来创建其他子应用 `subapp-two` 和更多。 #### 2. 配置 Webpack 或 Vite 支持跨域加载 对于微前端来说,通常会有一个主容器应用负责协调各个子应用之间的交互。这里假设使用 Vite 来构建整个体系[^4]。 在主项目的根目录下安装依赖: ```bash npm init vite-app main-container && cd $_ npm install @single-spa/webpack single-spa-vue qiankun ``` 编辑 `vite.config.js` 文件配置公共路径设置以适应不同环境下的资源定位需求: ```javascript import { defineConfig } from 'vite' export default defineConfig({ base: process.env.NODE_ENV === 'production' ? '/main-container/' : '/', }) ``` #### 3. 注册并启动子应用 修改主容器内的入口文件 `src/main.ts`, 使用 Single SPA 库注册各子应用实例,并定义其生命周期钩子函数[^2]: ```typescript // src/main.ts import { registerApplication, start } from "single-spa"; registerApplication( "subappOne", () => import("@/apps/subapp-one"), (location) => location.pathname.startsWith("/one") ); start(); ``` 同样地,在每一个子应用里也需要做一些适配工作以便能够被正确识别为 microfrontend 组件的一部分。比如调整各自的 manifest.json 及 index.html 中 script 标签的引入方式等。 #### 4. 处理全局状态管理和通信机制 考虑到多应用间可能存在共享的数据模型或业务逻辑调用场景,则可以通过 Vuex Store 或者更轻量级的消息总线如 mitt 来解决这类问题[^3]. 例如可以在主容器中建立一个中心化的 store 并暴露给所有子应用访问;或者利用 event bus 方式让它们之间互相发送自定义事件完成必要的通知操作。 ```javascript // 创建mitt实例 const emitter = require('mitt')(); function broadcastEvent(eventName,data){ emitter.emit(eventName,data); } // 子应用订阅特定类型的广播消息 emitter.on('some-event', handlerFunction); ``` 通过以上步骤就可以初步建立起一套完整的 Vue3 微前端架构方案了。当然实际生产环境中还需要考虑诸如性能优化、安全性加固等多个方面因素的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值