事件机制(Event Mechanism)

JavaScript 事件机制是前端交互的核心,包括事件捕获、事件冒泡、事件委托等重要概念。

参考规范DOM Events


📚 目录


1. 事件概述

1.1 什么是事件

事件是用户或浏览器执行的某种动作,如点击、滚动、键盘输入等。

// 事件三要素
// 1. 事件源:触发事件的元素
// 2. 事件类型:click、scroll、keydown 等
// 3. 事件处理函数:事件发生时要执行的函数

1.2 事件绑定方式

// 方式 1:HTML 属性(不推荐)
// <button onclick="handleClick()">Click</button>
 
// 方式 2:DOM 属性(不推荐,只能绑定一个)
element.onclick = function() {
  console.log('Clicked');
};
 
// 方式 3:addEventListener(推荐)
element.addEventListener('click', function(event) {
  console.log('Clicked');
});

2. 事件流

2.1 事件流的三个阶段

事件流描述了事件从触发到处理的完整过程,包含三个阶段:

  1. 捕获阶段(Capture Phase):从 window 到目标元素
  2. 目标阶段(Target Phase):在目标元素上
  3. 冒泡阶段(Bubble Phase):从目标元素到 window
window → document → html → body → div → button
  ↓                                    ↑
  捕获阶段                          冒泡阶段

2.2 事件捕获

// 在捕获阶段触发(第三个参数为 true)
element.addEventListener('click', function(event) {
  console.log('Capture phase');
}, true);
 
// 事件捕获顺序:window → document → html → body → div → button

2.3 事件冒泡

// 在冒泡阶段触发(默认,第三个参数为 false)
element.addEventListener('click', function(event) {
  console.log('Bubble phase');
});
 
// 事件冒泡顺序:button → div → body → html → document → window

2.4 阻止事件传播

element.addEventListener('click', function(event) {
  // 阻止事件继续传播(捕获或冒泡)
  event.stopPropagation();
  
  // 阻止立即传播(立即停止,包括同一元素的其他监听器)
  event.stopImmediatePropagation();
});

2.5 事件目标

element.addEventListener('click', function(event) {
  // event.target - 实际触发事件的元素
  console.log('Target:', event.target);
  
  // event.currentTarget - 当前处理事件的元素(this 指向)
  console.log('Current target:', event.currentTarget);
  
  // event.target === event.currentTarget 时,事件在目标元素上触发
});

3. 事件对象

3.1 Event 对象

所有事件对象都继承自 Event 对象。

element.addEventListener('click', function(event) {
  // 事件类型
  event.type;  // "click"
  
  // 事件目标
  event.target;        // 触发事件的元素
  event.currentTarget; // 当前处理事件的元素
  
  // 事件阶段
  event.eventPhase;    // 1=捕获, 2=目标, 3=冒泡
  
  // 时间戳
  event.timeStamp;     // 事件发生的时间戳
  
  // 是否可冒泡
  event.bubbles;       // true/false
  
  // 是否可取消
  event.cancelable;    // true/false
});

3.2 阻止默认行为

// 阻止默认行为(如链接跳转、表单提交)
element.addEventListener('click', function(event) {
  event.preventDefault();
  
  // 检查是否已阻止
  event.defaultPrevented;  // true
});
 
// 示例:阻止链接跳转
link.addEventListener('click', function(event) {
  event.preventDefault();
  console.log('Link click prevented');
});

3.3 鼠标事件对象

element.addEventListener('click', function(event) {
  // 鼠标位置(相对于视口)
  event.clientX;  // X 坐标
  event.clientY;  // Y 坐标
  
  // 鼠标位置(相对于页面)
  event.pageX;    // X 坐标
  event.pageY;    // Y 坐标
  
  // 鼠标位置(相对于屏幕)
  event.screenX;  // X 坐标
  event.screenY;  // Y 坐标
  
  // 鼠标按钮
  event.button;   // 0=左键, 1=中键, 2=右键
  event.buttons;  // 按下的按钮组合(位掩码)
  
  // 修饰键
  event.ctrlKey;   // Ctrl 键
  event.shiftKey;  // Shift 键
  event.altKey;    // Alt 键
  event.metaKey;   // Meta 键(Mac 的 Cmd)
});

3.4 键盘事件对象

element.addEventListener('keydown', function(event) {
  // 按键代码
  event.key;        // "a", "Enter", "ArrowUp" 等(推荐)
  event.code;       // "KeyA", "Enter", "ArrowUp" 等
  event.keyCode;    // 数字代码(已废弃)
  
  // 修饰键
  event.ctrlKey;
  event.shiftKey;
  event.altKey;
  event.metaKey;
  
  // 是否重复按键
  event.repeat;     // true/false
});

4. 事件委托

4.1 什么是事件委托

事件委托(Event Delegation)是将事件监听器绑定到父元素,利用事件冒泡机制处理子元素的事件。

4.2 事件委托的优势

// ❌ 不好:为每个子元素绑定事件
const items = document.querySelectorAll('.item');
items.forEach(item => {
  item.addEventListener('click', handleClick);
});
 
// ✅ 好:事件委托
const list = document.querySelector('.list');
list.addEventListener('click', function(event) {
  // event.target 是实际点击的元素
  if (event.target.matches('.item')) {
    handleClick(event);
  }
});

优势

  • 减少事件监听器数量(性能更好)
  • 动态添加的元素自动支持事件
  • 减少内存占用

4.3 事件委托实现

// 方式 1:使用 matches
parent.addEventListener('click', function(event) {
  if (event.target.matches('.item')) {
    console.log('Item clicked');
  }
});
 
// 方式 2:使用 closest(向上查找)
parent.addEventListener('click', function(event) {
  const item = event.target.closest('.item');
  if (item) {
    console.log('Item clicked:', item);
  }
});
 
// 方式 3:检查类名
parent.addEventListener('click', function(event) {
  if (event.target.classList.contains('item')) {
    console.log('Item clicked');
  }
});

5. 事件类型

5.1 鼠标事件

// click - 单击
element.addEventListener('click', handler);
 
// dblclick - 双击
element.addEventListener('dblclick', handler);
 
// mousedown - 鼠标按下
element.addEventListener('mousedown', handler);
 
// mouseup - 鼠标释放
element.addEventListener('mouseup', handler);
 
// mousemove - 鼠标移动
element.addEventListener('mousemove', handler);
 
// mouseenter - 鼠标进入(不冒泡)
element.addEventListener('mouseenter', handler);
 
// mouseleave - 鼠标离开(不冒泡)
element.addEventListener('mouseleave', handler);
 
// mouseover - 鼠标悬停(冒泡)
element.addEventListener('mouseover', handler);
 
// mouseout - 鼠标移出(冒泡)
element.addEventListener('mouseout', handler);
 
// contextmenu - 右键菜单
element.addEventListener('contextmenu', handler);

5.2 键盘事件

// keydown - 按键按下
element.addEventListener('keydown', handler);
 
// keyup - 按键释放
element.addEventListener('keyup', handler);
 
// keypress - 按键按下(已废弃,不推荐)
element.addEventListener('keypress', handler);

5.3 表单事件

// submit - 表单提交
form.addEventListener('submit', function(event) {
  event.preventDefault();  // 阻止默认提交
});
 
// change - 值改变(失去焦点时)
input.addEventListener('change', handler);
 
// input - 值改变(实时)
input.addEventListener('input', handler);
 
// focus - 获得焦点
input.addEventListener('focus', handler);
 
// blur - 失去焦点
input.addEventListener('blur', handler);

5.4 滚动事件

// scroll - 滚动
window.addEventListener('scroll', function(event) {
  console.log('Scrolled:', window.scrollY);
});
 
// ⚠️ 性能优化:使用节流
let ticking = false;
window.addEventListener('scroll', function() {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      // 滚动处理逻辑
      ticking = false;
    });
    ticking = true;
  }
});

5.5 窗口事件

// resize - 窗口大小改变
window.addEventListener('resize', handler);
 
// load - 页面加载完成
window.addEventListener('load', handler);
 
// DOMContentLoaded - DOM 加载完成
document.addEventListener('DOMContentLoaded', handler);
 
// beforeunload - 页面卸载前
window.addEventListener('beforeunload', handler);

6. 自定义事件

6.1 创建自定义事件

// 方式 1:使用 Event 构造函数
const event = new Event('customEvent', {
  bubbles: true,
  cancelable: true
});
 
// 方式 2:使用 CustomEvent 构造函数(可传递数据)
const customEvent = new CustomEvent('customEvent', {
  detail: { message: 'Hello' },
  bubbles: true,
  cancelable: true
});
 
// 触发事件
element.dispatchEvent(customEvent);

6.2 监听自定义事件

element.addEventListener('customEvent', function(event) {
  console.log('Custom event:', event.detail);
});

6.3 事件总线模式

// 简单的事件总线实现
class EventBus {
  constructor() {
    this.events = {};
  }
  
  on(event, handler) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(handler);
  }
  
  off(event, handler) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(h => h !== handler);
    }
  }
  
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(handler => handler(data));
    }
  }
}
 
// 使用
const bus = new EventBus();
bus.on('userLogin', (user) => console.log('User logged in:', user));
bus.emit('userLogin', { name: 'John' });

7. 事件性能优化

7.1 事件节流(Throttle)

// 节流:限制函数执行频率
function throttle(func, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}
 
// 使用
window.addEventListener('scroll', throttle(function() {
  console.log('Scrolled');
}, 100));

7.2 事件防抖(Debounce)

// 防抖:延迟执行,在连续触发时只执行最后一次
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}
 
// 使用
input.addEventListener('input', debounce(function() {
  console.log('Input changed');
}, 300));

7.3 被动事件监听器

// passive: true - 提升滚动性能
// 告诉浏览器事件处理函数不会调用 preventDefault()
element.addEventListener('touchstart', handler, {
  passive: true
});
 
// 检查是否支持 passive
let supportsPassive = false;
try {
  const opts = Object.defineProperty({}, 'passive', {
    get() {
      supportsPassive = true;
    }
  });
  window.addEventListener('test', null, opts);
} catch (e) {}

7.4 一次性事件监听器

// once: true - 只执行一次
element.addEventListener('click', handler, {
  once: true
});
 
// 手动移除
element.addEventListener('click', function handler(event) {
  console.log('Clicked');
  element.removeEventListener('click', handler);
});

📖 参考资源


javascript 事件 事件机制 前端基础