Skip to content

DOM 事件

事件的概念

DOM 事件是用户与网页交互时触发的动作,如点击、鼠标移动、键盘输入等。事件处理是 JavaScript 与网页交互的核心部分,它允许我们响应用户的操作并执行相应的代码。

事件类型

1. 鼠标事件

事件名称描述
click鼠标点击元素
dblclick鼠标双击元素
mouseover鼠标指针移动到元素上
mouseout鼠标指针离开元素
mousedown鼠标按钮被按下
mouseup鼠标按钮被释放
mousemove鼠标指针在元素上移动
contextmenu鼠标右键点击元素(打开上下文菜单)

2. 键盘事件

事件名称描述
keydown键盘按键被按下
keyup键盘按键被释放
keypress键盘按键被按下并释放(已废弃)

3. 表单事件

事件名称描述
submit表单提交
reset表单重置
change表单元素的值改变
input表单元素的值输入
focus表单元素获得焦点
blur表单元素失去焦点
select文本被选择

4. 文档事件

事件名称描述
load文档或资源加载完成
unload文档或资源卸载(已废弃)
DOMContentLoadedDOM 加载完成,无需等待图片等资源
resize窗口大小改变
scroll页面滚动
error资源加载错误

5. 触摸事件

事件名称描述
touchstart触摸开始
touchmove触摸移动
touchend触摸结束
touchcancel触摸被取消

事件处理程序

1. HTML 事件处理器

直接在 HTML 元素中添加事件处理器(不推荐):

html
<button onclick="alert('Button clicked!')">点击我</button>

<script>
  function handleClick() {
    alert("Button clicked!");
  }
</script>
<button onclick="handleClick()">点击我</button>

注意:HTML 事件处理器会使 HTML 和 JavaScript 代码耦合,难以维护,应尽量避免使用。

2. DOM 0 级事件处理器

通过元素的属性添加事件处理器:

javascript
const button = document.getElementById("btn");

// 添加事件处理器
button.onclick = function () {
  console.log("Button clicked!");
};

// 移除事件处理器
button.onclick = null;

注意:DOM 0 级事件处理器只能为每个事件添加一个处理程序,后添加的会覆盖前面的。

3. DOM 2 级事件处理器

使用 addEventListener()removeEventListener() 方法添加和移除事件处理器:

javascript
const button = document.getElementById("btn");

// 添加事件处理器
function handleClick() {
  console.log("Button clicked!");
}

button.addEventListener("click", handleClick);

// 移除事件处理器
button.removeEventListener("click", handleClick);

// 添加匿名函数作为事件处理器(无法移除)
button.addEventListener("click", function () {
  console.log("Button clicked!");
});

注意:DOM 2 级事件处理器可以为同一个事件添加多个处理程序,它们会按照添加的顺序执行。

事件对象

当事件触发时,浏览器会创建一个事件对象(Event 对象),并将其作为参数传递给事件处理程序:

javascript
const button = document.getElementById("btn");

button.addEventListener("click", function (event) {
  console.log("事件类型:", event.type); // 输出: click
  console.log("目标元素:", event.target); // 输出: <button id="btn">...</button>
  console.log("当前元素:", event.currentTarget); // 输出: <button id="btn">...</button>
  console.log("鼠标X坐标:", event.clientX); // 输出: 鼠标在视口中的X坐标
  console.log("鼠标Y坐标:", event.clientY); // 输出: 鼠标在视口中的Y坐标
});

事件对象的常用属性

属性描述
type事件类型
target事件的目标元素
currentTarget当前正在处理事件的元素(即添加事件监听器的元素)
bubbles事件是否冒泡
cancelable事件是否可取消
clientX/clientY鼠标在视口中的坐标
pageX/pageY鼠标在页面中的坐标
keyCode键盘按键的代码(已废弃,使用 key 或 code)
key键盘按键的名称
code键盘按键的物理代码
preventDefault()阻止事件的默认行为
stopPropagation()阻止事件冒泡
stopImmediatePropagation()阻止事件冒泡并阻止其他事件监听器执行

事件流

事件流描述了事件在 DOM 树中的传播过程,包括三个阶段:

  1. 捕获阶段:事件从文档根节点向下传播到目标元素
  2. 目标阶段:事件到达目标元素
  3. 冒泡阶段:事件从目标元素向上传播到文档根节点

事件捕获

默认情况下,事件监听器在冒泡阶段触发。可以通过设置 addEventListener() 的第三个参数为 true 来在捕获阶段触发:

javascript
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");

// 在捕获阶段触发
outer.addEventListener(
  "click",
  function () {
    console.log("Outer clicked (capture)");
  },
  true
);

// 在冒泡阶段触发
outer.addEventListener(
  "click",
  function () {
    console.log("Outer clicked (bubble)");
  },
  false
);

// 在冒泡阶段触发(默认)
inner.addEventListener("click", function () {
  console.log("Inner clicked");
});

// 点击 inner 元素时的输出顺序:
// Outer clicked (capture)
// Inner clicked
// Outer clicked (bubble)

事件冒泡

事件冒泡是指事件从目标元素向上传播到文档根节点的过程:

javascript
const grandparent = document.getElementById("grandparent");
const parent = document.getElementById("parent");
const child = document.getElementById("child");

grandparent.addEventListener("click", function () {
  console.log("Grandparent clicked");
});

parent.addEventListener("click", function () {
  console.log("Parent clicked");
});

child.addEventListener("click", function () {
  console.log("Child clicked");
});

// 点击 child 元素时的输出顺序:
// Child clicked
// Parent clicked
// Grandparent clicked

阻止事件冒泡

使用 stopPropagation() 方法阻止事件冒泡:

javascript
const grandparent = document.getElementById("grandparent");
const parent = document.getElementById("parent");
const child = document.getElementById("child");

grandparent.addEventListener("click", function () {
  console.log("Grandparent clicked");
});

parent.addEventListener("click", function (event) {
  console.log("Parent clicked");
  event.stopPropagation(); // 阻止事件冒泡
});

child.addEventListener("click", function () {
  console.log("Child clicked");
});

// 点击 child 元素时的输出顺序:
// Child clicked
// Parent clicked
// Grandparent clicked 不会输出(因为事件冒泡被阻止)

事件委托

事件委托是一种事件处理模式,它利用事件冒泡的特性,将事件监听器添加到父元素上,而不是每个子元素上:

javascript
const container = document.getElementById("container");

// 为每个子元素添加事件监听器(不好的做法)
const items = document.querySelectorAll(".item");
items.forEach((item) => {
  item.addEventListener("click", function () {
    console.log("Item clicked:", this.textContent);
  });
});

// 使用事件委托(好的做法)
container.addEventListener("click", function (event) {
  if (event.target.classList.contains("item")) {
    console.log("Item clicked:", event.target.textContent);
  }
});

事件委托的优点

  1. 减少事件监听器的数量:只需在父元素上添加一个事件监听器,而不是每个子元素上都添加
  2. 动态元素支持:新添加的子元素会自动继承事件处理,无需重新添加事件监听器
  3. 提高性能:减少了内存使用和事件处理的开销

阻止默认行为

使用 preventDefault() 方法阻止事件的默认行为:

javascript
const link = document.getElementById("link");

link.addEventListener("click", function (event) {
  event.preventDefault(); // 阻止链接跳转
  console.log("Link clicked, but not navigated");
});

const form = document.getElementById("form");

form.addEventListener("submit", function (event) {
  event.preventDefault(); // 阻止表单提交
  console.log("Form submitted, but not sent");
});

事件处理的最佳实践

1. 使用 DOM 2 级事件处理器

优先使用 addEventListener()removeEventListener() 方法:

javascript
// 好的做法:使用 addEventListener
const button = document.getElementById("btn");
button.addEventListener("click", handleClick);

// 不好的做法:使用 DOM 0 级事件处理器
const button = document.getElementById("btn");
button.onclick = handleClick;

2. 使用事件委托

对于多个相似元素的事件,使用事件委托:

javascript
// 好的做法:使用事件委托
const container = document.getElementById("container");
container.addEventListener("click", function (event) {
  if (event.target.classList.contains("item")) {
    console.log("Item clicked:", event.target.textContent);
  }
});

// 不好的做法:为每个元素添加事件监听器
const items = document.querySelectorAll(".item");
items.forEach((item) => {
  item.addEventListener("click", function () {
    console.log("Item clicked:", this.textContent);
  });
});

3. 正确处理事件对象

使用事件对象的属性和方法来处理事件:

javascript
// 好的做法:使用 event.target
const container = document.getElementById("container");
container.addEventListener("click", function (event) {
  if (event.target.matches(".item")) {
    console.log("Item clicked:", event.target.textContent);
  }
});

// 不好的做法:使用 this
const items = document.querySelectorAll(".item");
items.forEach((item) => {
  item.addEventListener("click", function () {
    console.log("Item clicked:", this.textContent);
  });
});

4. 清理事件监听器

在不需要事件监听器时,及时移除它们,避免内存泄漏:

javascript
// 好的做法:清理事件监听器
function handleClick() {
  console.log("Button clicked");
}

const button = document.getElementById("btn");
button.addEventListener("click", handleClick);

// 不再需要时移除
button.removeEventListener("click", handleClick);

// 不好的做法:使用匿名函数,无法移除
const button = document.getElementById("btn");
button.addEventListener("click", function () {
  console.log("Button clicked");
});

5. 使用 passive 事件监听器

对于不会调用 preventDefault() 的事件监听器,使用 passive 选项可以提高性能:

javascript
// 好的做法:使用 passive 事件监听器
window.addEventListener(
  "scroll",
  function () {
    console.log("Scrolled");
  },
  { passive: true }
);

// 不好的做法:不使用 passive
window.addEventListener("scroll", function () {
  console.log("Scrolled");
});

小结

DOM 事件是 JavaScript 与网页交互的核心部分,它允许我们响应用户的操作并执行相应的代码。理解事件的类型、处理程序、事件流和委托等概念,是学习 JavaScript 前端开发的重要基础。在实际开发中,应该遵循最佳实践,使用合适的事件处理方式,提高代码的性能和可维护性。