Appearance
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 | 文档或资源卸载(已废弃) |
| DOMContentLoaded | DOM 加载完成,无需等待图片等资源 |
| 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 树中的传播过程,包括三个阶段:
- 捕获阶段:事件从文档根节点向下传播到目标元素
- 目标阶段:事件到达目标元素
- 冒泡阶段:事件从目标元素向上传播到文档根节点
事件捕获
默认情况下,事件监听器在冒泡阶段触发。可以通过设置 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);
}
});事件委托的优点
- 减少事件监听器的数量:只需在父元素上添加一个事件监听器,而不是每个子元素上都添加
- 动态元素支持:新添加的子元素会自动继承事件处理,无需重新添加事件监听器
- 提高性能:减少了内存使用和事件处理的开销
阻止默认行为
使用 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 前端开发的重要基础。在实际开发中,应该遵循最佳实践,使用合适的事件处理方式,提高代码的性能和可维护性。