阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 事件发射器模式

事件发射器模式

作者:陈川 阅读数:25239人阅读 分类: Node.js

事件发射器模式

事件发射器模式是Node.js中处理异步事件的核心机制之一。它允许对象发布命名事件,其他对象可以监听这些事件并做出响应。这种模式解耦了事件触发器和事件处理器之间的关系,使得代码更加模块化和可维护。

事件发射器的工作原理

Node.js中的events模块提供了EventEmitter类,它是实现事件发射器模式的基础。任何继承自EventEmitter的对象都可以成为事件发射器:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

事件发射器主要有两个核心方法:

  • emit(eventName[, ...args]):触发指定事件
  • on(eventName, listener):为指定事件添加监听器

基本用法示例

下面是一个简单的事件发射器使用示例:

const EventEmitter = require('events');

class Door extends EventEmitter {
  open() {
    console.log('门开了');
    this.emit('open', new Date());
  }
  
  close() {
    console.log('门关了');
    this.emit('close', new Date());
  }
}

const frontDoor = new Door();

// 添加事件监听器
frontDoor.on('open', (time) => {
  console.log(`有人在 ${time} 打开了门`);
});

frontDoor.on('close', (time) => {
  console.log(`有人在 ${time} 关上了门`);
});

// 触发事件
frontDoor.open();
setTimeout(() => frontDoor.close(), 1000);

事件发射器的特性

同步执行监听器

事件发射器会同步调用所有监听器,按照它们被注册的顺序执行:

const emitter = new EventEmitter();

emitter.on('event', () => console.log('第一个监听器'));
emitter.on('event', () => console.log('第二个监听器'));

emitter.emit('event');
// 输出:
// 第一个监听器
// 第二个监听器

一次性监听器

使用once()方法可以注册只触发一次的监听器:

const emitter = new EventEmitter();

emitter.once('login', (user) => {
  console.log(`${user} 首次登录`);
});

emitter.emit('login', '张三'); // 会触发
emitter.emit('login', '张三'); // 不会触发

错误事件处理

EventEmittererror事件有特殊处理。如果没有为error事件注册监听器,当触发error事件时,Node.js会抛出异常并退出进程:

const emitter = new EventEmitter();

// 必须处理error事件
emitter.on('error', (err) => {
  console.error('发生错误:', err.message);
});

emitter.emit('error', new Error('出错了!'));

高级用法

获取监听器信息

const emitter = new EventEmitter();

const listener = () => console.log('事件触发');
emitter.on('event', listener);

console.log(emitter.listenerCount('event')); // 1
console.log(emitter.eventNames()); // ['event']

移除监听器

const emitter = new EventEmitter();

function listener() {
  console.log('事件触发');
  emitter.removeListener('event', listener);
}

emitter.on('event', listener);
emitter.emit('event'); // 会触发
emitter.emit('event'); // 不会触发

设置最大监听器数量

默认情况下,每个事件最多可以有10个监听器。超过这个数量会输出警告:

const emitter = new EventEmitter();
emitter.setMaxListeners(20); // 增加到20个

实际应用场景

HTTP服务器

Node.js的HTTP服务器就是基于事件发射器构建的:

const http = require('http');

const server = http.createServer();

server.on('request', (req, res) => {
  res.end('Hello World');
});

server.listen(3000);

流处理

Node.js的流也是事件发射器的实例:

const fs = require('fs');

const readStream = fs.createReadStream('file.txt');

readStream.on('data', (chunk) => {
  console.log(`接收到 ${chunk.length} 字节数据`);
});

readStream.on('end', () => {
  console.log('没有更多数据了');
});

自定义事件总线

可以创建一个全局事件总线来实现不同模块间的通信:

// eventBus.js
const EventEmitter = require('events');
module.exports = new EventEmitter();

// moduleA.js
const bus = require('./eventBus');
bus.emit('data-ready', { data: 'some data' });

// moduleB.js
const bus = require('./eventBus');
bus.on('data-ready', (data) => {
  console.log('接收到数据:', data);
});

性能考虑

避免内存泄漏

忘记移除监听器可能导致内存泄漏:

class Resource {
  constructor() {
    this.emitter = new EventEmitter();
    this.emitter.on('data', this.handleData);
  }
  
  handleData = (data) => {
    console.log(data);
  }
  
  destroy() {
    // 必须移除监听器
    this.emitter.removeListener('data', this.handleData);
  }
}

批量操作优化

当需要触发多个事件时,可以考虑批量处理:

const emitter = new EventEmitter();

// 不推荐
for (let i = 0; i < 1000; i++) {
  emitter.emit('data', i);
}

// 推荐
const batch = [];
for (let i = 0; i < 1000; i++) {
  batch.push(i);
}
emitter.emit('batch-data', batch);

与其他模式的比较

与回调模式比较

事件发射器模式比简单的回调模式更灵活:

// 回调模式
function fetchData(callback) {
  // ...获取数据
  callback(data);
}

// 事件发射器模式
class DataFetcher extends EventEmitter {
  fetch() {
    // ...获取数据
    this.emit('data', data);
    this.emit('end');
  }
}

与Promise/async-await比较

事件发射器适合处理多个离散事件,而Promise更适合单次异步操作:

// Promise - 单次操作
function getUser(id) {
  return new Promise((resolve, reject) => {
    // ...获取用户
    resolve(user);
  });
}

// 事件发射器 - 持续事件
class UserTracker extends EventEmitter {
  track(userId) {
    setInterval(() => {
      const status = checkUserStatus(userId);
      this.emit('status-change', status);
    }, 1000);
  }
}

扩展EventEmitter

可以通过继承来扩展EventEmitter的功能:

class EnhancedEmitter extends EventEmitter {
  emitWithLog(event, ...args) {
    console.log(`触发事件: ${event}`);
    return this.emit(event, ...args);
  }
  
  waitFor(event, timeout = 5000) {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error('等待事件超时'));
      }, timeout);
      
      this.once(event, (...args) => {
        clearTimeout(timer);
        resolve(args);
      });
    });
  }
}

// 使用示例
const emitter = new EnhancedEmitter();
emitter.emitWithLog('test', 1, 2, 3);

(async () => {
  try {
    const result = await emitter.waitFor('data');
    console.log(result);
  } catch (err) {
    console.error(err);
  }
})();

浏览器中的事件发射器

虽然Node.js的events模块是服务器端的,但类似的概念也存在于浏览器中:

// 自定义事件发射器类
class BrowserEventEmitter {
  constructor() {
    this.listeners = {};
  }
  
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }
  
  emit(event, ...args) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(cb => cb(...args));
    }
  }
}

// 使用示例
const emitter = new BrowserEventEmitter();
emitter.on('click', (x, y) => {
  console.log(`点击位置: ${x}, ${y}`);
});

emitter.emit('click', 100, 200);

测试事件发射器

测试事件发射器时,可以使用模拟或间谍函数:

const assert = require('assert');
const EventEmitter = require('events');

describe('EventEmitter测试', () => {
  it('应该触发事件并调用监听器', () => {
    const emitter = new EventEmitter();
    let called = false;
    
    emitter.on('test', () => {
      called = true;
    });
    
    emitter.emit('test');
    assert.strictEqual(called, true);
  });
  
  it('应该传递正确的参数', (done) => {
    const emitter = new EventEmitter();
    
    emitter.on('data', (a, b) => {
      try {
        assert.strictEqual(a, 1);
        assert.strictEqual(b, 2);
        done();
      } catch (err) {
        done(err);
      }
    });
    
    emitter.emit('data', 1, 2);
  });
});

常见问题与解决方案

事件监听器过多

当事件监听器数量过多时,可以考虑使用事件聚合:

const emitter = new EventEmitter();

// 原始方式 - 多个独立事件
emitter.on('user-added', handleUserAdded);
emitter.on('user-updated', handleUserUpdated);

// 改进方式 - 聚合事件
emitter.on('user-change', (event) => {
  switch (event.type) {
    case 'added':
      handleUserAdded(event.user);
      break;
    case 'updated':
      handleUserUpdated(event.user);
      break;
  }
});

事件顺序依赖

当事件有顺序依赖时,可以使用异步控制流库:

const { AsyncSeriesWaterfallHook } = require('tapable');

const hook = new AsyncSeriesWaterfallHook(['data']);

hook.tapAsync('第一步', (data, callback) => {
  setTimeout(() => {
    data.step1 = '完成';
    callback(null, data);
  }, 100);
});

hook.tapAsync('第二步', (data, callback) => {
  setTimeout(() => {
    data.step2 = '完成';
    callback(null, data);
  }, 50);
});

hook.callAsync({}, (err, result) => {
  console.log(result);
});

跨进程事件

对于跨进程的事件通信,可以使用消息队列或专门的库:

// 使用Redis实现跨进程事件总线
const redis = require('redis');
const subscriber = redis.createClient();
const publisher = redis.createClient();

subscriber.on('message', (channel, message) => {
  console.log(`接收到消息: ${message} 来自频道: ${channel}`);
});

subscriber.subscribe('notifications');

// 在另一个进程中
publisher.publish('notifications', 'Hello from Process B');

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌