事件基础概念

什么是事件

事件是用户或浏览器执行的某种动作,如点击、键盘输入、页面加载等。JavaScript通过事件处理机制来响应这些动作,实现交互功能。

// 事件的基本组成
// 1. 事件源(Event Target):触发事件的元素
// 2. 事件类型(Event Type):事件的名称,如click、keydown等
// 3. 事件处理器(Event Handler):响应事件的函数
// 4. 事件对象(Event Object):包含事件信息的对象

// 基本事件处理示例
const button = document.createElement('button');
button.textContent = '点击我';
document.body.appendChild(button);

// 方式1:HTML属性(不推荐)
// <button onclick="handleClick()">点击我</button>

// 方式2:DOM属性
button.onclick = function(event) {
  console.log('按钮被点击了!');
  console.log('事件类型:', event.type);
  console.log('事件目标:', event.target);
};

// 方式3:addEventListener(推荐)
button.addEventListener('click', function(event) {
  console.log('使用addEventListener处理点击事件');
});

// 事件对象的常用属性
button.addEventListener('click', function(event) {
  console.log('事件对象属性:');
  console.log('type:', event.type); // 事件类型
  console.log('target:', event.target); // 事件目标
  console.log('currentTarget:', event.currentTarget); // 当前处理事件的元素
  console.log('timeStamp:', event.timeStamp); // 事件发生的时间戳
  console.log('bubbles:', event.bubbles); // 是否冒泡
  console.log('cancelable:', event.cancelable); // 是否可取消
});

事件类型分类

// 创建测试元素
const testDiv = document.createElement('div');
testDiv.style.cssText = `
  width: 200px;
  height: 100px;
  background-color: lightblue;
  border: 2px solid blue;
  margin: 10px;
  padding: 10px;
  cursor: pointer;
`;
testDiv.textContent = '测试区域';
document.body.appendChild(testDiv);

const testInput = document.createElement('input');
testInput.type = 'text';
testInput.placeholder = '输入测试';
document.body.appendChild(testInput);

// 1. 鼠标事件
const mouseEvents = {
  click: '单击',
  dblclick: '双击',
  mousedown: '鼠标按下',
  mouseup: '鼠标释放',
  mousemove: '鼠标移动',
  mouseover: '鼠标进入(冒泡)',
  mouseout: '鼠标离开(冒泡)',
  mouseenter: '鼠标进入(不冒泡)',
  mouseleave: '鼠标离开(不冒泡)',
  contextmenu: '右键菜单',
  wheel: '滚轮滚动'
};

Object.entries(mouseEvents).forEach(([eventType, description]) => {
  testDiv.addEventListener(eventType, function(event) {
    console.log(`${description} - 坐标: (${event.clientX}, ${event.clientY})`);
    
    // 鼠标事件特有属性
    if (event.type === 'click') {
      console.log('按钮:', event.button); // 0:左键, 1:中键, 2:右键
      console.log('按键组合:', {
        ctrlKey: event.ctrlKey,
        shiftKey: event.shiftKey,
        altKey: event.altKey,
        metaKey: event.metaKey
      });
    }
  });
});

// 2. 键盘事件
const keyboardEvents = {
  keydown: '按键按下',
  keyup: '按键释放',
  keypress: '按键按下(已废弃,使用keydown代替)'
};

Object.entries(keyboardEvents).forEach(([eventType, description]) => {
  testInput.addEventListener(eventType, function(event) {
    console.log(`${description}:`, {
      key: event.key, // 按键值
      code: event.code, // 物理按键代码
      keyCode: event.keyCode, // 按键码(已废弃)
      ctrlKey: event.ctrlKey,
      shiftKey: event.shiftKey,
      altKey: event.altKey
    });
    
    // 常用按键检测
    if (event.key === 'Enter') {
      console.log('回车键被按下');
    } else if (event.key === 'Escape') {
      console.log('ESC键被按下');
    } else if (event.ctrlKey && event.key === 's') {
      event.preventDefault(); // 阻止默认保存行为
      console.log('Ctrl+S 快捷键');
    }
  });
});

// 3. 表单事件
const formEvents = {
  focus: '获得焦点',
  blur: '失去焦点',
  input: '输入内容变化',
  change: '值改变',
  submit: '表单提交',
  reset: '表单重置'
};

Object.entries(formEvents).forEach(([eventType, description]) => {
  if (eventType === 'input' || eventType === 'change' || eventType === 'focus' || eventType === 'blur') {
    testInput.addEventListener(eventType, function(event) {
      console.log(`${description} - 当前值:`, event.target.value);
    });
  }
});

// 4. 窗口和文档事件
const windowEvents = {
  load: '页面加载完成',
  DOMContentLoaded: 'DOM加载完成',
  beforeunload: '页面即将卸载',
  unload: '页面卸载',
  resize: '窗口大小改变',
  scroll: '滚动',
  hashchange: 'URL哈希改变',
  popstate: '历史状态改变'
};

// 注意:这些事件通常绑定到window或document对象
window.addEventListener('resize', function(event) {
  console.log('窗口大小:', window.innerWidth, 'x', window.innerHeight);
});

document.addEventListener('DOMContentLoaded', function(event) {
  console.log('DOM加载完成');
});

// 5. 触摸事件(移动设备)
const touchEvents = {
  touchstart: '触摸开始',
  touchmove: '触摸移动',
  touchend: '触摸结束',
  touchcancel: '触摸取消'
};

Object.entries(touchEvents).forEach(([eventType, description]) => {
  testDiv.addEventListener(eventType, function(event) {
    console.log(`${description}:`, {
      touches: event.touches.length, // 当前触摸点数量
      changedTouches: event.changedTouches.length // 改变的触摸点数量
    });
    
    // 阻止默认行为(如滚动)
    event.preventDefault();
  }, { passive: false }); // passive: false 允许preventDefault
});

事件监听器

addEventListener详解

// addEventListener语法
// element.addEventListener(type, listener, options)
// element.addEventListener(type, listener, useCapture)

const eventButton = document.createElement('button');
eventButton.textContent = '事件测试按钮';
document.body.appendChild(eventButton);

// 基本用法
eventButton.addEventListener('click', function(event) {
  console.log('基本点击处理器');
});

// 使用箭头函数
eventButton.addEventListener('click', (event) => {
  console.log('箭头函数处理器');
});

// 使用命名函数
function handleClick(event) {
  console.log('命名函数处理器');
}
eventButton.addEventListener('click', handleClick);

// options参数详解
eventButton.addEventListener('click', function(event) {
  console.log('高级选项处理器');
}, {
  once: true, // 只执行一次
  passive: false, // 是否为被动监听器
  capture: false, // 是否在捕获阶段处理
  signal: null // AbortSignal对象,用于取消监听
});

// 使用AbortController取消事件监听
const controller = new AbortController();
const signal = controller.signal;

eventButton.addEventListener('click', function(event) {
  console.log('可取消的处理器');
}, { signal });

// 5秒后取消监听
setTimeout(() => {
  controller.abort();
  console.log('事件监听已取消');
}, 5000);

// 移除事件监听器
eventButton.removeEventListener('click', handleClick);

// 事件监听器管理类
class EventManager {
  constructor(element) {
    this.element = element;
    this.listeners = new Map();
  }
  
  on(type, handler, options = {}) {
    // 生成唯一标识
    const id = this.generateId();
    
    // 包装处理器以便追踪
    const wrappedHandler = (event) => {
      try {
        handler.call(this.element, event);
      } catch (error) {
        console.error('事件处理器错误:', error);
      }
    };
    
    // 存储监听器信息
    this.listeners.set(id, {
      type,
      handler: wrappedHandler,
      originalHandler: handler,
      options
    });
    
    // 添加监听器
    this.element.addEventListener(type, wrappedHandler, options);
    
    return id; // 返回ID用于后续移除
  }
  
  off(id) {
    const listener = this.listeners.get(id);
    if (listener) {
      this.element.removeEventListener(
        listener.type,
        listener.handler,
        listener.options
      );
      this.listeners.delete(id);
      return true;
    }
    return false;
  }
  
  offAll(type) {
    let removed = 0;
    for (const [id, listener] of this.listeners) {
      if (!type || listener.type === type) {
        this.off(id);
        removed++;
      }
    }
    return removed;
  }
  
  once(type, handler, options = {}) {
    return this.on(type, handler, { ...options, once: true });
  }
  
  emit(type, detail = null) {
    const event = new CustomEvent(type, { detail });
    this.element.dispatchEvent(event);
    return this;
  }
  
  getListeners(type) {
    const result = [];
    for (const [id, listener] of this.listeners) {
      if (!type || listener.type === type) {
        result.push({ id, ...listener });
      }
    }
    return result;
  }
  
  generateId() {
    return `listener_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
  
  destroy() {
    this.offAll();
    this.listeners.clear();
  }
}

// 使用事件管理器
const eventManager = new EventManager(eventButton);

// 添加多个监听器
const clickId1 = eventManager.on('click', function(event) {
  console.log('管理器处理器1');
});

const clickId2 = eventManager.on('click', function(event) {
  console.log('管理器处理器2');
});

const mouseoverId = eventManager.once('mouseover', function(event) {
  console.log('鼠标悬停(只执行一次)');
});

// 查看所有监听器
console.log('所有监听器:', eventManager.getListeners());
console.log('点击监听器:', eventManager.getListeners('click'));

// 移除特定监听器
// eventManager.off(clickId1);

// 触发自定义事件
eventManager.emit('custom-event', { message: '自定义数据' });
eventManager.on('custom-event', function(event) {
  console.log('自定义事件:', event.detail);
});

事件处理器的this绑定

// 创建测试元素
const thisTestButton = document.createElement('button');
thisTestButton.textContent = 'this绑定测试';
thisTestButton.id = 'this-test';
document.body.appendChild(thisTestButton);

// 1. 普通函数中的this
thisTestButton.addEventListener('click', function(event) {
  console.log('普通函数this:', this); // 指向button元素
  console.log('this === event.currentTarget:', this === event.currentTarget); // true
});

// 2. 箭头函数中的this
thisTestButton.addEventListener('click', (event) => {
  console.log('箭头函数this:', this); // 指向外层作用域的this(通常是window)
});

// 3. 对象方法作为事件处理器
const buttonHandler = {
  name: 'ButtonHandler',
  
  handleClick: function(event) {
    console.log('对象方法this:', this); // 指向button元素,不是buttonHandler对象
    console.log('无法访问name属性:', this.name); // undefined
  },
  
  handleClickBound: function(event) {
    console.log('绑定后的this:', this); // 指向buttonHandler对象
    console.log('可以访问name属性:', this.name); // 'ButtonHandler'
  }
};

// 直接使用对象方法(this会被重新绑定)
thisTestButton.addEventListener('click', buttonHandler.handleClick);

// 使用bind绑定this
thisTestButton.addEventListener('click', buttonHandler.handleClickBound.bind(buttonHandler));

// 4. 类中的事件处理
class ButtonController {
  constructor(button) {
    this.button = button;
    this.clickCount = 0;
    
    // 方式1:在构造函数中绑定this
    this.handleClick = this.handleClick.bind(this);
    button.addEventListener('click', this.handleClick);
    
    // 方式2:使用箭头函数(类字段)
    button.addEventListener('click', this.handleClickArrow);
  }
  
  handleClick(event) {
    this.clickCount++;
    console.log(`类方法处理点击,计数: ${this.clickCount}`);
  }
  
  // 箭头函数自动绑定this
  handleClickArrow = (event) => {
    console.log('箭头函数方法,this指向类实例:', this.constructor.name);
  }
  
  destroy() {
    this.button.removeEventListener('click', this.handleClick);
    this.button.removeEventListener('click', this.handleClickArrow);
  }
}

// 使用类控制器
const buttonController = new ButtonController(thisTestButton);

// 5. 手动控制this的工具函数
function bindEventHandler(element, eventType, handler, context) {
  const boundHandler = function(event) {
    return handler.call(context, event);
  };
  
  element.addEventListener(eventType, boundHandler);
  
  return function unbind() {
    element.removeEventListener(eventType, boundHandler);
  };
}

// 使用工具函数
const customContext = {
  message: 'Hello from custom context'
};

const unbindCustom = bindEventHandler(
  thisTestButton,
  'click',
  function(event) {
    console.log('自定义上下文:', this.message);
  },
  customContext
);

// 稍后可以取消绑定
// unbindCustom();

事件冒泡和捕获

事件流机制

// 创建嵌套元素结构
const outerDiv = document.createElement('div');
outerDiv.id = 'outer';
outerDiv.style.cssText = `
  padding: 30px;
  background-color: red;
  border: 2px solid darkred;
`;

const middleDiv = document.createElement('div');
middleDiv.id = 'middle';
middleDiv.style.cssText = `
  padding: 20px;
  background-color: yellow;
  border: 2px solid orange;
`;

const innerDiv = document.createElement('div');
innerDiv.id = 'inner';
innerDiv.style.cssText = `
  padding: 10px;
  background-color: lightblue;
  border: 2px solid blue;
  cursor: pointer;
`;
innerDiv.textContent = '点击我测试事件流';

// 构建嵌套结构
middleDiv.appendChild(innerDiv);
outerDiv.appendChild(middleDiv);
document.body.appendChild(outerDiv);

// 事件流演示
function createEventLogger(phase) {
  return function(event) {
    console.log(`${phase} - 元素: ${this.id}, 目标: ${event.target.id}, 当前: ${event.currentTarget.id}`);
  };
}

// 1. 捕获阶段(从外到内)
outerDiv.addEventListener('click', createEventLogger('捕获阶段 - outer'), true);
middleDiv.addEventListener('click', createEventLogger('捕获阶段 - middle'), true);
innerDiv.addEventListener('click', createEventLogger('捕获阶段 - inner'), true);

// 2. 冒泡阶段(从内到外)
innerDiv.addEventListener('click', createEventLogger('冒泡阶段 - inner'), false);
middleDiv.addEventListener('click', createEventLogger('冒泡阶段 - middle'), false);
outerDiv.addEventListener('click', createEventLogger('冒泡阶段 - outer'), false);

// 事件流控制
class EventFlowController {
  constructor() {
    this.logEnabled = true;
  }
  
  // 阻止事件冒泡
  stopPropagation(event) {
    event.stopPropagation();
    console.log('事件冒泡已阻止');
  }
  
  // 立即阻止事件传播(包括同级监听器)
  stopImmediatePropagation(event) {
    event.stopImmediatePropagation();
    console.log('事件传播已立即阻止');
  }
  
  // 阻止默认行为
  preventDefault(event) {
    event.preventDefault();
    console.log('默认行为已阻止');
  }
  
  // 同时阻止冒泡和默认行为
  stopAll(event) {
    event.stopPropagation();
    event.preventDefault();
    console.log('冒泡和默认行为都已阻止');
  }
  
  // 条件性阻止
  conditionalStop(event, condition) {
    if (condition) {
      this.stopPropagation(event);
    }
  }
}

const flowController = new EventFlowController();

// 添加控制按钮
const controlsDiv = document.createElement('div');
controlsDiv.style.margin = '10px 0';

const stopPropButton = document.createElement('button');
stopPropButton.textContent = '阻止冒泡';
stopPropButton.addEventListener('click', function() {
  // 为inner元素添加阻止冒泡的处理器
  innerDiv.addEventListener('click', function(event) {
    flowController.stopPropagation(event);
  }, { once: true });
  console.log('下次点击inner元素将阻止冒泡');
});

const stopImmediateButton = document.createElement('button');
stopImmediateButton.textContent = '立即阻止传播';
stopImmediateButton.addEventListener('click', function() {
  innerDiv.addEventListener('click', function(event) {
    flowController.stopImmediatePropagation(event);
  }, { once: true });
  console.log('下次点击inner元素将立即阻止传播');
});

controlsDiv.appendChild(stopPropButton);
controlsDiv.appendChild(stopImmediateButton);
document.body.appendChild(controlsDiv);

// 事件流分析工具
class EventFlowAnalyzer {
  constructor() {
    this.events = [];
    this.isRecording = false;
  }
  
  startRecording() {
    this.events = [];
    this.isRecording = true;
    console.log('开始记录事件流');
  }
  
  stopRecording() {
    this.isRecording = false;
    console.log('停止记录事件流');
    return this.events;
  }
  
  recordEvent(phase, element, target, currentTarget) {
    if (this.isRecording) {
      this.events.push({
        timestamp: Date.now(),
        phase,
        element: element.id || element.tagName,
        target: target.id || target.tagName,
        currentTarget: currentTarget.id || currentTarget.tagName
      });
    }
  }
  
  analyze() {
    console.log('事件流分析:');
    this.events.forEach((event, index) => {
      console.log(`${index + 1}. ${event.phase} - ${event.element} (目标: ${event.target})`);
    });
    
    return {
      totalEvents: this.events.length,
      capturePhase: this.events.filter(e => e.phase.includes('捕获')).length,
      bubblePhase: this.events.filter(e => e.phase.includes('冒泡')).length
    };
  }
  
  clear() {
    this.events = [];
  }
}

const analyzer = new EventFlowAnalyzer();

// 为分析器添加监听器
[outerDiv, middleDiv, innerDiv].forEach(element => {
  element.addEventListener('click', function(event) {
    analyzer.recordEvent('捕获阶段', this, event.target, event.currentTarget);
  }, true);
  
  element.addEventListener('click', function(event) {
    analyzer.recordEvent('冒泡阶段', this, event.target, event.currentTarget);
  }, false);
});

// 分析控制按钮
const analyzeButton = document.createElement('button');
analyzeButton.textContent = '开始分析';
analyzeButton.addEventListener('click', function() {
  if (analyzer.isRecording) {
    const events = analyzer.stopRecording();
    const analysis = analyzer.analyze();
    console.log('分析结果:', analysis);
    this.textContent = '开始分析';
  } else {
    analyzer.startRecording();
    this.textContent = '停止分析';
  }
});

controlsDiv.appendChild(analyzeButton);

事件委托

// 事件委托示例
const listContainer = document.createElement('ul');
listContainer.id = 'task-list';
listContainer.style.cssText = `
  border: 1px solid #ccc;
  padding: 10px;
  margin: 10px 0;
  list-style: none;
`;

// 创建初始列表项
for (let i = 1; i <= 3; i++) {
  const li = document.createElement('li');
  li.innerHTML = `
    <span>任务 ${i}</span>
    <button class="edit-btn" data-id="${i}">编辑</button>
    <button class="delete-btn" data-id="${i}">删除</button>
  `;
  li.style.cssText = 'margin: 5px 0; padding: 5px; border: 1px solid #eee;';
  listContainer.appendChild(li);
}

document.body.appendChild(listContainer);

// 传统方式:为每个按钮添加监听器(不推荐)
function addTraditionalListeners() {
  const editButtons = listContainer.querySelectorAll('.edit-btn');
  const deleteButtons = listContainer.querySelectorAll('.delete-btn');
  
  editButtons.forEach(button => {
    button.addEventListener('click', function(event) {
      const id = this.dataset.id;
      console.log(`传统方式 - 编辑任务 ${id}`);
    });
  });
  
  deleteButtons.forEach(button => {
    button.addEventListener('click', function(event) {
      const id = this.dataset.id;
      console.log(`传统方式 - 删除任务 ${id}`);
      this.parentElement.remove();
    });
  });
}

// 事件委托方式(推荐)
listContainer.addEventListener('click', function(event) {
  const target = event.target;
  
  // 检查点击的是否是编辑按钮
  if (target.classList.contains('edit-btn')) {
    const id = target.dataset.id;
    console.log(`事件委托 - 编辑任务 ${id}`);
    
    // 编辑逻辑
    const span = target.parentElement.querySelector('span');
    const currentText = span.textContent;
    const newText = prompt('编辑任务:', currentText);
    if (newText && newText !== currentText) {
      span.textContent = newText;
    }
  }
  
  // 检查点击的是否是删除按钮
  else if (target.classList.contains('delete-btn')) {
    const id = target.dataset.id;
    console.log(`事件委托 - 删除任务 ${id}`);
    
    if (confirm('确定要删除这个任务吗?')) {
      target.parentElement.remove();
    }
  }
});

// 事件委托管理器
class EventDelegator {
  constructor(container) {
    this.container = container;
    this.delegates = new Map();
    this.init();
  }
  
  init() {
    this.container.addEventListener('click', this.handleClick.bind(this));
    this.container.addEventListener('change', this.handleChange.bind(this));
    this.container.addEventListener('input', this.handleInput.bind(this));
    this.container.addEventListener('submit', this.handleSubmit.bind(this));
  }
  
  // 注册委托处理器
  delegate(selector, eventType, handler) {
    const key = `${eventType}:${selector}`;
    if (!this.delegates.has(key)) {
      this.delegates.set(key, []);
    }
    this.delegates.get(key).push(handler);
    return this;
  }
  
  // 取消委托
  undelegate(selector, eventType, handler) {
    const key = `${eventType}:${selector}`;
    const handlers = this.delegates.get(key);
    if (handlers) {
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
      if (handlers.length === 0) {
        this.delegates.delete(key);
      }
    }
    return this;
  }
  
  // 处理点击事件
  handleClick(event) {
    this.executeHandlers('click', event);
  }
  
  // 处理change事件
  handleChange(event) {
    this.executeHandlers('change', event);
  }
  
  // 处理input事件
  handleInput(event) {
    this.executeHandlers('input', event);
  }
  
  // 处理submit事件
  handleSubmit(event) {
    this.executeHandlers('submit', event);
  }
  
  // 执行匹配的处理器
  executeHandlers(eventType, event) {
    for (const [key, handlers] of this.delegates) {
      const [type, selector] = key.split(':');
      if (type === eventType) {
        // 检查事件目标是否匹配选择器
        if (event.target.matches(selector)) {
          handlers.forEach(handler => {
            try {
              handler.call(event.target, event);
            } catch (error) {
              console.error('委托处理器错误:', error);
            }
          });
        }
        
        // 检查事件目标的祖先元素是否匹配选择器
        const matchedAncestor = event.target.closest(selector);
        if (matchedAncestor && matchedAncestor !== event.target) {
          handlers.forEach(handler => {
            try {
              handler.call(matchedAncestor, event);
            } catch (error) {
              console.error('委托处理器错误:', error);
            }
          });
        }
      }
    }
  }
  
  // 获取所有委托信息
  getDelegates() {
    const result = {};
    for (const [key, handlers] of this.delegates) {
      result[key] = handlers.length;
    }
    return result;
  }
  
  // 清除所有委托
  clear() {
    this.delegates.clear();
    return this;
  }
}

// 使用事件委托管理器
const delegator = new EventDelegator(listContainer);

// 注册委托处理器
delegator
  .delegate('.edit-btn', 'click', function(event) {
    console.log('委托管理器 - 编辑按钮点击');
  })
  .delegate('.delete-btn', 'click', function(event) {
    console.log('委托管理器 - 删除按钮点击');
  })
  .delegate('li', 'click', function(event) {
    console.log('委托管理器 - 列表项点击');
  });

// 动态添加新项目的按钮
const addButton = document.createElement('button');
addButton.textContent = '添加新任务';
addButton.addEventListener('click', function() {
  const taskCount = listContainer.children.length + 1;
  const li = document.createElement('li');
  li.innerHTML = `
    <span>任务 ${taskCount}</span>
    <button class="edit-btn" data-id="${taskCount}">编辑</button>
    <button class="delete-btn" data-id="${taskCount}">删除</button>
  `;
  li.style.cssText = 'margin: 5px 0; padding: 5px; border: 1px solid #eee;';
  listContainer.appendChild(li);
  
  console.log('新任务已添加,事件委托自动生效');
});

document.body.appendChild(addButton);

console.log('当前委托信息:', delegator.getDelegates());

自定义事件

创建和触发自定义事件

// 创建测试元素
const customEventDiv = document.createElement('div');
customEventDiv.id = 'custom-event-test';
customEventDiv.style.cssText = `
  padding: 20px;
  background-color: lightgreen;
  border: 2px solid green;
  margin: 10px 0;
  cursor: pointer;
`;
customEventDiv.textContent = '自定义事件测试区域';
document.body.appendChild(customEventDiv);

// 1. 基本自定义事件
const basicEvent = new Event('myCustomEvent');

// 监听自定义事件
customEventDiv.addEventListener('myCustomEvent', function(event) {
  console.log('基本自定义事件被触发');
  console.log('事件类型:', event.type);
  console.log('是否冒泡:', event.bubbles);
  console.log('是否可取消:', event.cancelable);
});

// 触发自定义事件
customEventDiv.dispatchEvent(basicEvent);

// 2. 带数据的自定义事件(CustomEvent)
const dataEvent = new CustomEvent('dataEvent', {
  detail: {
    message: 'Hello from custom event',
    timestamp: Date.now(),
    data: { id: 1, name: 'Test' }
  },
  bubbles: true,
  cancelable: true
});

customEventDiv.addEventListener('dataEvent', function(event) {
  console.log('带数据的自定义事件:');
  console.log('详细信息:', event.detail);
  console.log('消息:', event.detail.message);
  console.log('数据:', event.detail.data);
});

customEventDiv.dispatchEvent(dataEvent);

// 3. 自定义事件管理器
class CustomEventManager {
  constructor(element) {
    this.element = element;
    this.eventHistory = [];
  }
  
  // 创建并触发事件
  emit(eventType, detail = null, options = {}) {
    const eventOptions = {
      bubbles: true,
      cancelable: true,
      ...options
    };
    
    let event;
    if (detail !== null) {
      event = new CustomEvent(eventType, {
        detail,
        ...eventOptions
      });
    } else {
      event = new Event(eventType, eventOptions);
    }
    
    // 记录事件历史
    this.eventHistory.push({
      type: eventType,
      detail,
      timestamp: Date.now(),
      bubbles: event.bubbles,
      cancelable: event.cancelable
    });
    
    // 触发事件
    const result = this.element.dispatchEvent(event);
    
    return {
      event,
      defaultPrevented: event.defaultPrevented,
      propagationStopped: !result
    };
  }
  
  // 监听事件
  on(eventType, handler, options = {}) {
    this.element.addEventListener(eventType, handler, options);
    return this;
  }
  
  // 移除监听
  off(eventType, handler, options = {}) {
    this.element.removeEventListener(eventType, handler, options);
    return this;
  }
  
  // 一次性监听
  once(eventType, handler, options = {}) {
    this.element.addEventListener(eventType, handler, {
      ...options,
      once: true
    });
    return this;
  }
  
  // 获取事件历史
  getHistory(eventType = null) {
    if (eventType) {
      return this.eventHistory.filter(event => event.type === eventType);
    }
    return [...this.eventHistory];
  }
  
  // 清除历史
  clearHistory() {
    this.eventHistory = [];
    return this;
  }
  
  // 批量触发事件
  emitBatch(events) {
    const results = [];
    events.forEach(({ type, detail, options }) => {
      results.push(this.emit(type, detail, options));
    });
    return results;
  }
  
  // 条件触发
  emitIf(condition, eventType, detail, options) {
    if (typeof condition === 'function' ? condition() : condition) {
      return this.emit(eventType, detail, options);
    }
    return null;
  }
}

// 使用自定义事件管理器
const eventManager = new CustomEventManager(customEventDiv);

// 注册多个监听器
eventManager
  .on('user:login', function(event) {
    console.log('用户登录事件:', event.detail);
  })
  .on('user:logout', function(event) {
    console.log('用户登出事件:', event.detail);
  })
  .on('data:update', function(event) {
    console.log('数据更新事件:', event.detail);
  });

// 触发事件
eventManager.emit('user:login', {
  userId: 123,
  username: 'john_doe',
  loginTime: new Date().toISOString()
});

eventManager.emit('data:update', {
  table: 'users',
  action: 'insert',
  recordId: 456
});

// 批量触发
eventManager.emitBatch([
  {
    type: 'notification:show',
    detail: { message: '欢迎回来!', type: 'success' }
  },
  {
    type: 'analytics:track',
    detail: { event: 'page_view', page: '/dashboard' }
  }
]);

// 4. 事件总线(全局事件系统)
class EventBus {
  constructor() {
    this.events = new Map();
    this.onceEvents = new Map();
    this.middlewares = [];
  }
  
  // 添加中间件
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  // 监听事件
  on(eventType, handler) {
    if (!this.events.has(eventType)) {
      this.events.set(eventType, []);
    }
    this.events.get(eventType).push(handler);
    return this;
  }
  
  // 一次性监听
  once(eventType, handler) {
    if (!this.onceEvents.has(eventType)) {
      this.onceEvents.set(eventType, []);
    }
    this.onceEvents.get(eventType).push(handler);
    return this;
  }
  
  // 移除监听
  off(eventType, handler) {
    if (this.events.has(eventType)) {
      const handlers = this.events.get(eventType);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
    return this;
  }
  
  // 触发事件
  emit(eventType, ...args) {
    // 应用中间件
    let eventData = { type: eventType, args, timestamp: Date.now() };
    for (const middleware of this.middlewares) {
      eventData = middleware(eventData);
      if (!eventData) return false; // 中间件可以阻止事件
    }
    
    let handled = false;
    
    // 执行普通监听器
    if (this.events.has(eventType)) {
      const handlers = this.events.get(eventType);
      handlers.forEach(handler => {
        try {
          handler(...eventData.args);
          handled = true;
        } catch (error) {
          console.error(`事件处理器错误 (${eventType}):`, error);
        }
      });
    }
    
    // 执行一次性监听器
    if (this.onceEvents.has(eventType)) {
      const handlers = this.onceEvents.get(eventType);
      handlers.forEach(handler => {
        try {
          handler(...eventData.args);
          handled = true;
        } catch (error) {
          console.error(`一次性事件处理器错误 (${eventType}):`, error);
        }
      });
      this.onceEvents.delete(eventType); // 清除一次性监听器
    }
    
    return handled;
  }
  
  // 获取所有事件类型
  getEventTypes() {
    const types = new Set();
    for (const type of this.events.keys()) {
      types.add(type);
    }
    for (const type of this.onceEvents.keys()) {
      types.add(type);
    }
    return Array.from(types);
  }
  
  // 清除所有监听器
  clear() {
    this.events.clear();
    this.onceEvents.clear();
    return this;
  }
  
  // 获取监听器数量
  getListenerCount(eventType) {
    const regular = this.events.has(eventType) ? this.events.get(eventType).length : 0;
    const once = this.onceEvents.has(eventType) ? this.onceEvents.get(eventType).length : 0;
    return regular + once;
  }
}

// 创建全局事件总线
const globalEventBus = new EventBus();

// 添加日志中间件
globalEventBus.use((eventData) => {
  console.log(`[EventBus] ${eventData.type} 事件被触发:`, eventData.args);
  return eventData;
});

// 添加过滤中间件
globalEventBus.use((eventData) => {
  // 过滤掉某些事件
  if (eventData.type.startsWith('debug:') && !window.DEBUG_MODE) {
    return null; // 阻止事件
  }
  return eventData;
});

// 使用事件总线
globalEventBus
  .on('app:start', function() {
    console.log('应用启动');
  })
  .on('user:action', function(action, data) {
    console.log(`用户操作: ${action}`, data);
  })
  .once('app:ready', function() {
    console.log('应用就绪(只执行一次)');
  });

// 触发事件
globalEventBus.emit('app:start');
globalEventBus.emit('user:action', 'click', { button: 'submit' });
globalEventBus.emit('app:ready');
globalEventBus.emit('app:ready'); // 不会再次执行

console.log('事件总线状态:');
console.log('所有事件类型:', globalEventBus.getEventTypes());
console.log('user:action 监听器数量:', globalEventBus.getListenerCount('user:action'));

// 点击测试区域触发自定义事件
customEventDiv.addEventListener('click', function() {
  eventManager.emit('user:click', {
    x: Math.random() * 100,
    y: Math.random() * 100,
    timestamp: Date.now()
  });
  
  globalEventBus.emit('ui:interaction', 'click', {
    element: 'custom-event-div'
  });
});

console.log('事件历史:', eventManager.getHistory());

本章总结

本章深入探讨了JavaScript的事件处理机制:

  1. 事件基础:理解了事件的组成要素、事件类型分类和事件对象的属性
  2. 事件监听器:掌握了addEventListener的使用方法、选项参数和this绑定
  3. 事件流:学习了事件的捕获和冒泡机制,以及如何控制事件传播
  4. 事件委托:了解了事件委托的原理和优势,以及如何实现高效的事件管理
  5. 自定义事件:掌握了创建、触发和管理自定义事件的方法
  6. 高级模式:通过事件管理器、事件总线等模式展示了企业级事件处理方案

事件处理是Web应用交互的核心,熟练掌握这些概念和技术能够帮助我们构建响应迅速、用户体验良好的应用程序。这些知识为后续学习框架和库的事件系统奠定了重要基础。

下一章我们将学习异步编程的高级主题,包括Promise链式调用、async/await最佳实践和错误处理策略。