likes
comments
collection
share

Nodejs 知识体系(四): EventEmitter前端事件驱动:EventEmitter 的应用与场景 Event

作者站长头像
站长
· 阅读数 41

前端事件驱动:EventEmitter 的应用与场景

EventEmitter 是 Node.js 中用于实现事件驱动编程的核心模块,允许对象订阅和发布事件,从而实现异步操作和事件处理。

在前端开发中,最常见的场景是用户点击事件触发多个操作,例如 Update1、Update2、Update3。在这种情况下,Update 函数的设计要考虑以下几点:

  • Update 函数本质上应定义在组件内部,以确保组件的封装性和控制能力。
  • 由于 Update 函数可能涉及到内部逻辑或状态更新,不希望通过 props 暴露,从而避免对组件整体设计结构产生影响。

Nodejs 知识体系(四): EventEmitter前端事件驱动:EventEmitter 的应用与场景 Event

// Module1 定义了一个处理 'submit' 事件的函数 update1,并将其注册到 eventBus 中
const Module1 = () => {
    const update1 = function() {
        // TODO: 在这里实现 update1 的具体功能
    }
    eventBus.on('submit', update1)  // 监听 'submit' 事件,并在事件触发时调用 update1
}

// Module2 定义了一个处理 'submit' 事件的函数 update2,并将其注册到 eventBus 中
const Module2 = () => {
    const update2 = function() {
        // TODO: 在这里实现 update2 的具体功能
    }
    eventBus.on('submit', update2)  // 监听 'submit' 事件,并在事件触发时调用 update2
}

// Module3 定义了一个处理 'submit' 事件的函数 update3,并将其注册到 eventBus 中
const Module3 = () => {
    const update3 = function() {
        // TODO: 在这里实现 update3 的具体功能
    }
    eventBus.on('submit', update3)  // 监听 'submit' 事件,并在事件触发时调用 update3
}

// 按钮组件:触发 'submit' 事件的函数 handleSumbmit
// 当按钮被点击时,它会发出 'submit' 事件,所有注册到 eventBus 的监听器(如 update1、update2 和 update3)都会被同步调用
handleSubmit() {
    eventBus.emit('submit', ...args)  // 触发 'submit' 事件,并传递参数 args 给事件处理函数
}

Module1、Module2 和 Module3 都注册了对应的 update 函数到 eventBus 上,监听 'submit' 事件。

handleSubmit 函数触发了 'submit' 事件,所有监听该事件的处理函数会被同步调用,并接收传递的参数。

实现中心化事件管理机制:创建 EventBus

首先,我们需要实现中心化的事件管理机制,这可以通过创建一个 EventBus 来完成。

当然它也有缺点, EventBus 的缺点是它可能导致事件管理混乱,尤其是在大型应用中,难以追踪事件流和调试问题。

Nodejs 知识体系(四): EventEmitter前端事件驱动:EventEmitter 的应用与场景 Event

实现一个 EventEmiiter

为了实现一个基本的事件驱动机制,我们可以创建一个自定义的 EventEmitter。EventEmitter 的核心是一个事件总线(EventBus),它允许我们注册事件处理函数、触发事件以及注销事件处理函数。这个实现将包括以下几个关键方法:

  • on:用于注册事件处理函数,使其在事件触发时被调用。
  • emit:用于触发事件,并将事件相关的数据传递给所有注册的处理函数。
  • off:用于注销已经注册的事件处理函数,以便在事件触发时不再调用。
  • once:用于注册一个一次性事件处理函数,该函数在事件触发时调用一次后自动注销。

接下来,我们将详细实现这些方法,并通过实例化 EventEmitter 类来演示如何使用它。

初始化事件存储对象

// 创建一个 EventEmitter 类的构造函数
// 该函数初始化一个内部对象 _events,用于存储所有事件及其对应的处理函数
function EventEmitter() {
  this._events = {}; // 存储事件名称及其处理函数的映射关系
}

实现订阅事件

EventEmitter.prototype.on = function (eventName, cb) {
  // 初始化事件存储对象(如果尚未初始化)
  if (!this._events) this._events = {};
  
  // 获取当前事件的处理函数列表,如果不存在则初始化为空数组
  let eventList = this._events[eventName] || (this._events[eventName] = []);
  
  // 将回调函数添加到事件处理函数列表中
  eventList.push(cb);
};

实现触发事件

EventEmitter.prototype.emit = function (eventName, ...rest) {
  // 检查是否有对应的事件处理函数列表
  if (this._events[eventName]) {
    // 遍历事件处理函数列表并调用每个处理函数,传递事件参数
    this._events[eventName].forEach((cb) => cb(...rest));
  }
};

移除事件监听器

EventEmitter.prototype.off = function (eventName, cb) {
  if (this._events[eventName]) {
    // 从事件处理函数列表中移除指定的回调函数
    this._events[eventName] = this._events[eventName].filter((item) => item !== cb);
  }
};

添加一次性事件监听器

EventEmitter.prototype.once = function (eventName, cb) {
  const once = (...rest) => {
    cb(...rest);
    this.off(eventName, once);
  };
  this.on(eventName, once);
};

测试 EventEmitter 功能

const eventBus = new EventEmitter();

// 定义处理函数
const handle1 = function (...msg) {
  console.log(`handle1 ${msg}`);
};
const handle2 = function (...msg) {
  console.log(`handle2 ${msg}`);
};

// 订阅 'foo' 事件并注册处理函数
eventBus.on('foo', handle1);
eventBus.on('foo', handle2);

// 触发 'foo' 事件,所有注册的处理函数都会被调用
eventBus.emit('foo', 'data1', 'data2');

// 订阅 'fee' 事件并注册一次性处理函数
eventBus.once('fee', handle1);
eventBus.once('fee', handle2);

// 触发 'fee' 事件,所有一次性处理函数都会被调用
eventBus.emit('fee', 'data1', 'data2');
// 再次触发 'fee' 事件,处理函数不会被调用,因为它们是一次性事件处理函数
eventBus.emit('fee', 'data1', 'data2'); // 不再执行
eventBus.emit('fee', 'data1', 'data2'); // 不再执行

结语

在本篇文章中,我们实现了一个自定义的 EventEmitter,它是事件驱动编程的核心工具。通过构建这个 EventEmitter,我们能够有效管理事件的订阅、触发、注销以及一次性处理。这种实现为前端开发中的模块间通信提供了一种灵活且高效的解决方案,帮助我们更好地处理异步操作和事件流。

转载自:https://juejin.cn/post/7415914023278051367
评论
请登录