什么是DOM事件?

想象一下,你在网页上点击一个按钮、移动鼠标或按下键盘,这些操作都会触发事件(Event)。DOM事件就是浏览器中发生的这些特定交互的瞬间。

JavaScript可以通过事件处理程序(Event Handler)来”监听”这些事件,并在事件发生时执行特定的代码。这样就能创建出与用户交互的网页应用。

举个栗子🌰

当用户点击按钮时,显示一个提示框:

// 获取按钮元素
const button = document.querySelector(‘#myButton’);

// 添加点击事件监听器
button.addEventListener(‘click’, function() {
  alert(‘按钮被点击了!’);
});

事件处理程序

事件处理程序是响应事件的JavaScript函数。有三种主要方式添加事件处理程序:

1. HTML属性方式(不推荐)

直接在HTML元素中添加事件属性:

<button onclick=“alert(‘点击事件!’)”>
  点击我
</button>

缺点:HTML和JavaScript代码混合,难以维护。

2. DOM属性方式

通过JavaScript设置元素的on[event]属性:

const btn = document.getElementById(‘myBtn’);
btn.onclick = function() {
  console.log(‘按钮被点击了’);
};

缺点:一个元素只能有一个同类型事件处理程序。

3. addEventListener()(推荐)

现代JavaScript推荐的方式:

element.addEventListener(‘事件类型’, 处理函数, 是否捕获阶段);

优点:可以添加多个处理程序,更灵活控制事件阶段。

事件流(冒泡与捕获)

当事件发生时,它会沿着DOM树传播,这个过程分为三个阶段:

  1. 捕获阶段(Capture Phase):事件从window对象向下传播到目标元素
  2. 目标阶段(Target Phase):事件到达目标元素
  3. 冒泡阶段(Bubble Phase):事件从目标元素向上传播回window对象

可视化事件流

Window
↓↑

Document
↓↑

目标元素

↑ 捕获阶段向下传播,冒泡阶段向上传播 ↑

默认情况下,事件处理程序在冒泡阶段执行。可以通过addEventListener的第三个参数设置为true来在捕获阶段处理事件。

事件对象详解

当事件发生时,浏览器会创建一个事件对象(Event Object),其中包含事件相关的所有信息。这个对象会作为参数传递给事件处理函数。

常用属性和方法:

  • event.type:事件类型(如”click”)
  • event.target:触发事件的元素
  • event.currentTarget:当前处理事件的元素
  • event.preventDefault():阻止默认行为
  • event.stopPropagation():停止事件传播
  • event.clientX / event.clientY:鼠标事件发生时的坐标
  • event.key:键盘事件按下的键
// 使用事件对象示例
document.addEventListener(‘click’, function(event) {
  // event就是事件对象
  console.log(‘点击位置: X=’ + event.clientX + ‘, Y=’ + event.clientY);
  console.log(‘目标元素: ‘ + event.target.tagName);
});

常见事件类型

JavaScript支持多种事件类型,以下是最常用的:

鼠标事件

  • click:鼠标点击
  • dblclick:鼠标双击
  • mousedown:鼠标按下
  • mouseup:鼠标释放
  • mousemove:鼠标移动
  • mouseover:鼠标移入元素
  • mouseout:鼠标移出元素

键盘事件

  • keydown:按键按下
  • keyup:按键释放
  • keypress:按键按下并释放(已弃用)

表单事件

  • submit:表单提交
  • change:表单元素值改变
  • focus:元素获得焦点
  • blur:元素失去焦点
  • input:输入框值发生变化

其他事件

  • load:资源加载完成
  • scroll:滚动事件
  • resize:窗口大小改变

事件委托

事件委托是一种高效处理多个元素事件的技术。原理是利用事件冒泡,在父元素上设置事件监听器来处理子元素的事件。

为什么需要事件委托?

  • 减少事件处理程序数量,提高性能
  • 动态添加的子元素也能自动拥有事件处理
  • 代码更简洁,更易维护

事件委托示例

点击列表中任意项目,无需为每个项目单独添加事件:

// 父元素处理所有子元素点击
document.getElementById(‘myList’).addEventListener(‘click’, function(e) {
  // e.target是实际点击的元素
  if (e.target.tagName === ‘LI’) {
    console.log(‘点击了: ‘ + e.target.textContent);
  }
});

阻止默认行为

某些事件会触发浏览器的默认行为,例如:

  • 点击链接会导航到新页面
  • 提交表单会刷新页面
  • 右键点击会显示上下文菜单

可以使用event.preventDefault()来阻止这些默认行为:

// 阻止链接导航
document.querySelector(‘a’).addEventListener(‘click’, function(e) {
  e.preventDefault(); // 阻止默认导航行为
  console.log(‘链接点击被阻止’);
});

自定义事件

除了内置事件,你还可以创建自己的事件类型!

// 创建自定义事件
const myEvent = new CustomEvent(‘myCustomEvent’, {
  detail: { // 可以传递自定义数据
    message: ‘Hello from custom event!’,
    timestamp: new Date()
  },
  bubbles: true // 事件是否冒泡
});

// 监听自定义事件
element.addEventListener(‘myCustomEvent’, function(e) {
  console.log(e.detail.message);
});

// 触发自定义事件
element.dispatchEvent(myEvent);

最佳实践与注意事项

最佳实践:

  • 优先使用addEventListener而不是on[event]属性
  • 合理使用事件委托减少事件处理程序数量
  • 移除不再需要的事件监听器,避免内存泄漏
  • 在事件处理程序中避免长时间运行的操作
  • 使用事件对象的target和currentTarget属性正确识别事件源

常见错误:

  • 忘记使用event.preventDefault()时,事件继续传播导致意外行为
  • 在事件处理函数中使用this时,未正确绑定上下文
  • 添加过多事件处理程序导致性能问题
  • 未移除事件监听器导致内存泄漏

性能提示

对于频繁触发的事件(如scroll、mousemove、resize),使用防抖(debounce)或节流(throttle)技术来优化性能:

  • 防抖:事件触发后等待一段时间再执行,如果期间再次触发则重新计时
  • 节流:固定时间间隔内只执行一次事件处理