自定义事件

JavaScript 中创建和使用自定义事件的方法

问题

如何在 JavaScript 中创建和使用自定义事件?

解答

方式一:使用 CustomEvent

浏览器原生提供的自定义事件 API。

// 创建自定义事件
const event = new CustomEvent('myEvent', {
  detail: { message: 'Hello', id: 1 }, // 传递的数据
  bubbles: true,    // 是否冒泡
  cancelable: true  // 是否可取消
});

// 监听事件
document.addEventListener('myEvent', (e) => {
  console.log(e.detail); // { message: 'Hello', id: 1 }
});

// 触发事件
document.dispatchEvent(event);

方式二:手写事件系统(发布订阅模式)

class EventEmitter {
  constructor() {
    this.events = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
    return this; // 支持链式调用
  }

  // 触发事件
  emit(eventName, ...args) {
    const callbacks = this.events[eventName];
    if (callbacks) {
      callbacks.forEach(cb => cb(...args));
    }
    return this;
  }

  // 取消订阅
  off(eventName, callback) {
    const callbacks = this.events[eventName];
    if (callbacks) {
      this.events[eventName] = callbacks.filter(cb => cb !== callback);
    }
    return this;
  }

  // 只订阅一次
  once(eventName, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
    return this;
  }
}

// 使用示例
const emitter = new EventEmitter();

// 订阅
emitter.on('login', (user) => {
  console.log(`${user.name} 登录了`);
});

// 只触发一次
emitter.once('init', () => {
  console.log('初始化完成');
});

// 触发
emitter.emit('login', { name: '张三' }); // 张三 登录了
emitter.emit('init'); // 初始化完成
emitter.emit('init'); // 不会输出

方式三:继承 EventTarget

class MyComponent extends EventTarget {
  doSomething() {
    // 触发自定义事件
    this.dispatchEvent(new CustomEvent('done', {
      detail: { result: 'success' }
    }));
  }
}

const component = new MyComponent();

component.addEventListener('done', (e) => {
  console.log(e.detail.result); // success
});

component.doSomething();

关键点

  • CustomEvent 是浏览器原生 API,通过 detail 传递数据
  • 手写事件系统本质是发布订阅模式,用对象存储事件名和回调数组
  • once 实现:包装回调,执行后立即 off
  • EventTarget 可被继承,让自定义类拥有事件能力
  • 注意 off 时要移除正确的引用,匿名函数无法移除