自定义事件(CustomEvent)

CustomEvent API 允许开发者创建和触发自定义事件,实现组件间解耦通信。

参考规范DOM Living Standard - CustomEvent


📚 目录


1. CustomEvent 概述

1.1 什么是自定义事件

CustomEvent 是浏览器提供的用于创建自定义事件的接口,允许开发者定义自己的事件类型,实现组件间解耦通信。

特点

  • 解耦通信:组件间无需直接引用,通过事件通信
  • 灵活扩展:可以传递任意数据
  • 标准 API:基于 DOM 事件系统,兼容性好

1.2 与原生事件的区别

特性原生事件自定义事件
触发方式浏览器自动触发开发者手动触发
事件类型预定义(click、load 等)自定义命名
数据传递通过 event 对象通过 detail 属性
使用场景用户交互、页面生命周期组件通信、业务逻辑

2. 创建自定义事件

2.1 基本语法

// 创建自定义事件
const event = new CustomEvent('eventName', {
  detail: {
    // 自定义数据
  },
  bubbles: true,    // 是否冒泡
  cancelable: true  // 是否可取消
});

2.2 构造函数参数

CustomEvent(type, options)

  • type(必需):事件类型名称(字符串)
  • options(可选):事件配置对象
    • detail:传递给事件监听器的数据
    • bubbles:是否冒泡(默认 false
    • cancelable:是否可取消(默认 false

2.3 创建示例

// 创建简单自定义事件
const simpleEvent = new CustomEvent('myEvent');
 
// 创建带数据的自定义事件
const dataEvent = new CustomEvent('userAction', {
  detail: {
    action: 'click',
    timestamp: Date.now()
  }
});
 
// 创建可冒泡的自定义事件
const bubbleEvent = new CustomEvent('customClick', {
  detail: { message: 'Hello' },
  bubbles: true,
  cancelable: true
});

3. 触发自定义事件

3.1 在 window 上触发

// 创建自定义事件
const event = new CustomEvent('miniServiceAgreed', {
  detail: {
    userId: '123',
    timestamp: Date.now()
  }
});
 
// 在 window 上触发
window.dispatchEvent(event);

3.2 在 DOM 元素上触发

// 在特定元素上触发
const button = document.querySelector('#myButton');
const event = new CustomEvent('buttonClicked', {
  detail: { buttonId: 'myButton' }
});
 
button.dispatchEvent(event);

3.3 在 document 上触发

// 在 document 上触发
const event = new CustomEvent('pageReady', {
  detail: { page: 'home' }
});
 
document.dispatchEvent(event);

4. 监听自定义事件

4.1 使用 addEventListener

// 监听 window 上的自定义事件
window.addEventListener('miniServiceAgreed', (event) => {
  console.log('用户已同意服务协议');
  console.log('事件数据:', event.detail);
});
 
// 监听 DOM 元素上的自定义事件
const button = document.querySelector('#myButton');
button.addEventListener('buttonClicked', (event) => {
  console.log('按钮被点击:', event.detail);
});

4.2 获取事件数据

window.addEventListener('miniServiceAgreed', (event) => {
  // 通过 detail 属性获取数据
  const { userId, timestamp } = event.detail;
  
  console.log('用户ID:', userId);
  console.log('时间戳:', timestamp);
});

4.3 事件对象属性

window.addEventListener('customEvent', (event) => {
  // 事件类型
  console.log(event.type);        // 'customEvent'
  
  // 自定义数据
  console.log(event.detail);      // 传递的数据对象
  
  // 事件目标
  console.log(event.target);      // 触发事件的元素
  
  // 当前目标
  console.log(event.currentTarget); // 绑定监听器的元素
  
  // 是否冒泡
  console.log(event.bubbles);     // true/false
  
  // 是否可取消
  console.log(event.cancelable);  // true/false
});

5. 事件数据传递

5.1 传递简单数据

// 触发事件时传递数据
const event = new CustomEvent('dataEvent', {
  detail: {
    message: 'Hello World',
    count: 42
  }
});
 
window.dispatchEvent(event);
 
// 接收数据
window.addEventListener('dataEvent', (event) => {
  console.log(event.detail.message); // 'Hello World'
  console.log(event.detail.count);   // 42
});

5.2 传递复杂对象

// 传递复杂对象
const event = new CustomEvent('userEvent', {
  detail: {
    user: {
      id: '123',
      name: 'John',
      email: 'john@example.com'
    },
    action: 'login',
    metadata: {
      ip: '192.168.1.1',
      userAgent: navigator.userAgent
    }
  }
});
 
window.dispatchEvent(event);
 
// 接收复杂对象
window.addEventListener('userEvent', (event) => {
  const { user, action, metadata } = event.detail;
  console.log('用户:', user.name);
  console.log('操作:', action);
  console.log('元数据:', metadata);
});

5.3 传递函数(不推荐)

// 注意:detail 中传递函数会丢失上下文
const event = new CustomEvent('callbackEvent', {
  detail: {
    callback: function() {
      console.log('回调执行');
    }
  }
});
 
window.dispatchEvent(event);
 
// 接收函数
window.addEventListener('callbackEvent', (event) => {
  // 可以调用,但需要注意 this 绑定
  event.detail.callback();
});

6. 实际应用场景

6.1 组件间通信

// 组件 A:触发事件
class ComponentA {
  handleAgree() {
    const event = new CustomEvent('serviceAgreed', {
      detail: {
        userId: this.userId,
        timestamp: Date.now()
      }
    });
    window.dispatchEvent(event);
  }
}
 
// 组件 B:监听事件
class ComponentB {
  constructor() {
    window.addEventListener('serviceAgreed', this.onServiceAgreed.bind(this));
  }
  
  onServiceAgreed(event) {
    const { userId, timestamp } = event.detail;
    console.log('用户同意服务协议:', userId);
    // 执行相应逻辑
  }
}

6.2 状态同步

// 状态管理:触发状态变更事件
function updateUserStatus(status) {
  const event = new CustomEvent('userStatusChanged', {
    detail: {
      status: status,
      previousStatus: currentStatus,
      timestamp: Date.now()
    }
  });
  window.dispatchEvent(event);
}
 
// 多个组件监听状态变更
window.addEventListener('userStatusChanged', (event) => {
  const { status } = event.detail;
  updateUI(status);
  updateAnalytics(status);
  updateCache(status);
});

6.3 跨框架通信

// React 组件触发事件
function ReactComponent() {
  const handleClick = () => {
    const event = new CustomEvent('reactAction', {
      detail: { action: 'buttonClick' }
    });
    window.dispatchEvent(event);
  };
  
  return <button onClick={handleClick}>Click</button>;
}
 
// Vue 组件监听事件
export default {
  mounted() {
    window.addEventListener('reactAction', this.handleReactAction);
  },
  methods: {
    handleReactAction(event) {
      console.log('React 触发的操作:', event.detail.action);
    }
  }
}

6.4 微前端通信

// 主应用:触发全局事件
function notifyMicroApp(data) {
  const event = new CustomEvent('microAppEvent', {
    detail: data,
    bubbles: true
  });
  window.dispatchEvent(event);
}
 
// 微应用:监听全局事件
window.addEventListener('microAppEvent', (event) => {
  const data = event.detail;
  // 处理来自主应用的数据
  handleMicroAppData(data);
});

7. 最佳实践

7.1 事件命名规范

// ✅ 好的命名:清晰、有意义的名称
new CustomEvent('userLoginSuccess');
new CustomEvent('cartItemAdded');
new CustomEvent('paymentCompleted');
 
// ❌ 不好的命名:模糊、无意义
new CustomEvent('event1');
new CustomEvent('action');
new CustomEvent('update');

7.2 使用命名空间

// 使用命名空间避免冲突
new CustomEvent('app:user:login');
new CustomEvent('app:cart:add');
new CustomEvent('app:payment:complete');
 
// 监听时使用命名空间
window.addEventListener('app:user:login', handler);

7.3 事件清理

// 组件销毁时移除事件监听
class MyComponent {
  constructor() {
    this.handleEvent = this.handleEvent.bind(this);
    window.addEventListener('customEvent', this.handleEvent);
  }
  
  handleEvent(event) {
    // 处理事件
  }
  
  destroy() {
    // 清理事件监听
    window.removeEventListener('customEvent', this.handleEvent);
  }
}

7.4 错误处理

window.addEventListener('customEvent', (event) => {
  try {
    // 处理事件
    processEvent(event.detail);
  } catch (error) {
    console.error('处理自定义事件时出错:', error);
    // 错误上报
    reportError(error);
  }
});

7.5 类型检查

// 触发事件前验证数据
function triggerEvent(eventName, data) {
  if (!eventName || typeof eventName !== 'string') {
    throw new Error('事件名称必须是字符串');
  }
  
  const event = new CustomEvent(eventName, {
    detail: data,
    bubbles: true,
    cancelable: true
  });
  
  window.dispatchEvent(event);
}
 
// 监听事件时验证数据
window.addEventListener('customEvent', (event) => {
  if (!event.detail || typeof event.detail !== 'object') {
    console.warn('事件数据格式不正确');
    return;
  }
  
  // 处理事件
});

7.6 性能优化

// 使用防抖处理高频事件
function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}
 
// 防抖处理自定义事件
const debouncedHandler = debounce((event) => {
  console.log('处理事件:', event.detail);
}, 300);
 
window.addEventListener('highFrequencyEvent', debouncedHandler);

8. 与其他通信方式对比

8.1 CustomEvent vs 回调函数

特性CustomEvent回调函数
解耦性高(发布订阅模式)低(直接调用)
一对多支持不支持
异步性支持需要手动处理
适用场景组件间通信简单函数调用

8.2 CustomEvent vs EventEmitter

// CustomEvent:浏览器原生 API
const event = new CustomEvent('myEvent', { detail: data });
window.dispatchEvent(event);
 
// EventEmitter:Node.js 风格(需要库支持)
const emitter = new EventEmitter();
emitter.emit('myEvent', data);

8.3 CustomEvent vs 全局状态管理

// CustomEvent:事件驱动
window.dispatchEvent(new CustomEvent('stateChange', { detail: newState }));
 
// 全局状态管理:直接访问状态
store.setState(newState);
const state = store.getState();

9. 兼容性

9.1 浏览器支持

  • Chrome:15+
  • Firefox:11+
  • Safari:6+
  • Edge:12+
  • IE:不支持(需要使用 polyfill)

9.2 Polyfill

// 简单的 CustomEvent polyfill
(function() {
  if (typeof window.CustomEvent === 'function') {
    return;
  }
  
  function CustomEvent(type, params) {
    params = params || { bubbles: false, cancelable: false, detail: null };
    const event = document.createEvent('CustomEvent');
    event.initCustomEvent(type, params.bubbles, params.cancelable, params.detail);
    return event;
  }
  
  window.CustomEvent = CustomEvent;
})();

10. 相关链接

10.1 相关文档

  • 事件机制 — 事件捕获、冒泡、委托
  • DOM 操作 — DOM 接口和操作
  • BOM — window、document 等浏览器对象

10.2 参考资源


最后更新:2025
维护规范:每次新增笔记后,在对应 MOC 中加入链接


javascript 事件 自定义事件 customevent 组件通信 浏览器api dom