手把手教你实现一个EventEmitter,彻底告别复杂事件管理!

你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了?

别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%!

为什么要懂EventEmitter?

先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,就像开车不会踩油门,简直寸步难行!

而且,现在前端也越来越重视事件机制了。组件通信、状态管理,到处都能看到EventEmitter的影子。学会了它,你就能写出更优雅、更解耦的代码。

先来看看Node.js自带的events模块

在动手造轮子之前,我们先看看官方提供的events模块怎么用。这样你就能明白我们要实现什么功能了。

javascript 复制代码
// 引入events模块
const EventEmitter = require('events');

// 创建一个事件触发器实例
const myEmitter = new EventEmitter();

// 监听一个叫'greet'的事件
myEmitter.on('greet', (name) => {
    console.log(`Hello, ${name}!`);
});

// 触发greet事件
myEmitter.emit('greet', 'World'); // 输出: Hello, World!

看到了吗?就是这么简单!on方法用来监听事件,emit方法用来触发事件。这就是我们要实现的核心功能。

现在,让我们自己造一个EventEmitter!

准备好了吗?我们要开始写代码了!我会一步一步解释,保证你能跟上。

第一步:先搭个架子

任何类都是从构造函数开始的,我们先来定义基本的类结构:

javascript 复制代码
class MyEventEmitter {
    constructor() {
        // 这个对象用来存储所有的事件和对应的监听函数
        // 结构是这样的:{ 事件名: [函数1, 函数2, ...] }
        this.events = {};
    }
}

这里我们创建了一个events对象,它就像是一个登记簿,记录着每个事件都有哪些函数在监听。

第二步:实现on方法 - 监听事件

on方法就是用来注册事件监听器的,当特定事件发生时,对应的函数就会被调用。

javascript 复制代码
class MyEventEmitter {
    // ... 上面的构造函数
    
    on(eventName, listener) {
        // 如果这个事件还没有被注册过,就创建一个空数组来存放监听函数
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        
        // 把监听函数添加到对应事件的数组中
        this.events[eventName].push(listener);
        
        // 返回this,方便链式调用
        return this;
    }
}

来,我们测试一下这个on方法:

javascript 复制代码
const emitter = new MyEventEmitter();

// 监听一个'click'事件
emitter.on('click', (x, y) => {
    console.log(`点击了位置: (${x}, ${y})`);
});

// 还可以链式调用哦!
emitter
    .on('click', () => console.log('又点击了一次'))
    .on('hover', () => console.log('鼠标悬停了'));

第三步:实现emit方法 - 触发事件

emit方法就是用来触发事件的,它会调用所有监听这个事件的函数。

javascript 复制代码
class MyEventEmitter {
    // ... 上面的代码
    
    emit(eventName, ...args) {
        // 获取这个事件的所有监听函数
        const listeners = this.events[eventName];
        
        // 如果没有监听这个事件的函数,就直接返回
        if (!listeners || listeners.length === 0) {
            return false;
        }
        
        // 依次调用每个监听函数,并传入参数
        listeners.forEach(listener => {
            listener.apply(this, args);
        });
        
        return true;
    }
}

现在我们可以测试一下完整的流程了:

javascript 复制代码
const emitter = new MyEventEmitter();

// 监听事件
emitter.on('click', (x, y) => {
    console.log(`点击了位置: (${x}, ${y})`);
});

emitter.on('click', () => {
    console.log('又点击了一次');
});

// 触发事件
emitter.emit('click', 100, 200);
// 输出:
// 点击了位置: (100, 200)
// 又点击了一次

看!我们自己的EventEmitter已经能工作了!

第四步:实现off方法 - 移除监听器

有时候我们需要取消监听事件,不然可能会导致内存泄漏。这就需要一个off方法。

javascript 复制代码
class MyEventEmitter {
    // ... 上面的代码
    
    off(eventName, listenerToRemove) {
        // 如果没有监听这个事件,直接返回
        if (!this.events[eventName]) {
            return this;
        }
        
        // 过滤掉要移除的监听函数
        this.events[eventName] = this.events[eventName].filter(
            listener => listener !== listenerToRemove
        );
        
        return this;
    }
}

使用示例:

javascript 复制代码
const emitter = new MyEventEmitter();

function clickHandler() {
    console.log('点击处理函数');
}

// 添加监听
emitter.on('click', clickHandler);

// 移除监听
emitter.off('click', clickHandler);

// 现在触发click事件,什么都不会发生
emitter.emit('click');

第五步:实现once方法 - 只监听一次

有时候我们只需要监听一次事件,触发后就自动移除监听器。这个功能很常用!

javascript 复制代码
class MyEventEmitter {
    // ... 上面的代码
    
    once(eventName, listener) {
        // 创建一个只会执行一次的函数包装器
        const onceWrapper = (...args) => {
            // 先移除这个监听器
            this.off(eventName, onceWrapper);
            // 然后执行原始监听函数
            listener.apply(this, args);
        };
        
        // 注册这个包装器函数
        this.on(eventName, onceWrapper);
        
        return this;
    }
}

使用示例:

javascript 复制代码
const emitter = new MyEventEmitter();

// 这个函数只会执行一次
emitter.once('first-click', () => {
    console.log('第一次点击,很珍贵!');
});

emitter.emit('first-click'); // 输出: 第一次点击,很珍贵!
emitter.emit('first-click'); // 这次什么都不会输出

完整代码展示

现在把我们实现的所有功能整合在一起:

javascript 复制代码
class MyEventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(eventName, listener) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(listener);
        return this;
    }
    
    off(eventName, listenerToRemove) {
        if (!this.events[eventName]) {
            return this;
        }
        this.events[eventName] = this.events[eventName].filter(
            listener => listener !== listenerToRemove
        );
        return this;
    }
    
    once(eventName, listener) {
        const onceWrapper = (...args) => {
            this.off(eventName, onceWrapper);
            listener.apply(this, args);
        };
        this.on(eventName, onceWrapper);
        return this;
    }
    
    emit(eventName, ...args) {
        const listeners = this.events[eventName];
        if (!listeners || listeners.length === 0) {
            return false;
        }
        listeners.forEach(listener => {
            listener.apply(this, args);
        });
        return true;
    }
}

不到50行代码,我们就实现了一个功能完整的EventEmitter!是不是很有成就感?

实际应用场景

现在你可能会问:我学会了这个,到底能用在哪里呢?

场景一:组件通信 在前端框架中,非父子组件通信经常用EventEmitter来实现。

javascript 复制代码
// 创建一个全局的事件总线
const eventBus = new MyEventEmitter();

// 组件A发送消息
eventBus.emit('user-logged-in', userData);

// 组件B接收消息
eventBus.on('user-logged-in', (userData) => {
    console.log('用户登录了:', userData);
});

场景二:插件系统 如果你在写一个库或框架,可以用EventEmitter来实现插件系统。

javascript 复制代码
class MyFramework {
    constructor() {
        this.events = new MyEventEmitter();
    }
    
    // 框架内部在特定时机触发事件
    initialize() {
        // ... 初始化代码
        this.events.emit('initialized');
    }
}

// 插件可以监听框架事件
const plugin = {
    setup(framework) {
        framework.events.on('initialized', () => {
            console.log('框架初始化完成,插件开始工作!');
        });
    }
};

更进一步

我们的EventEmitter已经很好用了,但你还可以继续扩展它:

  1. 错误处理:添加error事件监听,避免未处理的错误导致程序崩溃
  2. 最大监听数限制:防止内存泄漏,设置单个事件的最大监听数
  3. 同步/异步触发:提供同步和异步两种事件触发方式
  4. 事件优先级:实现监听器的优先级系统

这些都是Node.js原生EventEmitter提供的功能,你有兴趣可以尝试实现一下!

总结

今天我们从零开始实现了一个完整的EventEmitter,掌握了事件驱动的核心原理。现在你应该明白:

  • EventEmitter的核心是发布-订阅模式
  • 通过on方法注册监听器,emit方法触发事件
  • off方法用于移除监听器,once用于一次性监听
  • 这种模式在前端和后端都有广泛应用

最重要的是,通过自己实现一遍,你再也不会对EventEmitter感到神秘了!下次面试被问到事件机制,你就能从容应对,把面试官讲得明明白白。

相关推荐
幸福摩天轮5 小时前
npm发布包
前端
前端AK君5 小时前
Gitlab 线上合并冲突的坑
前端
ze_juejin5 小时前
ES6 Module 深入学习
前端
章丸丸5 小时前
Tube - Studio Videos
前端·后端
因吹斯汀6 小时前
一饭封神:当AI厨神遇上你的冰箱,八大菜系大师在线battle!
前端·vue.js·ai编程
再学一点就睡6 小时前
NATAPP 内网穿透指南:让本地项目轻松 “走出去”
前端
拜无忧6 小时前
2025最新React项目架构指南:从零到一,为前端小白打造
前端·react.js·typescript
稻草人不怕疼6 小时前
记一次从“按钮点不动”到“窗口派发缺失”的排查过程
前端
前端小哲6 小时前
MCP从入门到实战
node.js·ai编程