DOM 事件机制
事件捕获、冒泡、事件委托及 target 与 currentTarget 的区别
问题
解释 DOM 事件机制,包括捕获、冒泡、事件委托,以及 target 和 currentTarget 的区别。
解答
事件流的三个阶段
DOM 事件传播分为三个阶段:
- 捕获阶段:从
window向下传播到目标元素 - 目标阶段:到达目标元素
- 冒泡阶段:从目标元素向上传播到
window
window
│
▼ 捕获阶段
document
│
▼
html
│
▼
body
│
▼
div (目标元素) ← 目标阶段
│
▼ 冒泡阶段
body
│
▼
...回到 window
捕获与冒泡
// addEventListener 第三个参数控制监听阶段
// true: 捕获阶段触发
// false(默认): 冒泡阶段触发
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
// 冒泡阶段触发(默认)
outer.addEventListener('click', () => {
console.log('outer 冒泡');
});
// 捕获阶段触发
outer.addEventListener('click', () => {
console.log('outer 捕获');
}, true);
inner.addEventListener('click', () => {
console.log('inner 冒泡');
});
inner.addEventListener('click', () => {
console.log('inner 捕获');
}, true);
// 点击 inner 时输出顺序:
// outer 捕获 -> inner 捕获 -> inner 冒泡 -> outer 冒泡
target 与 currentTarget
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', (e) => {
// target: 实际被点击的元素(事件源)
console.log('target:', e.target);
// currentTarget: 绑定事件处理器的元素(始终是 parent)
console.log('currentTarget:', e.currentTarget);
});
// 点击 child 时:
// target: <div id="child">
// currentTarget: <div id="parent">
// 点击 parent 时:
// target: <div id="parent">
// currentTarget: <div id="parent">
事件委托
利用冒泡机制,在父元素上统一处理子元素的事件:
<ul id="list">
<li data-id="1">Item 1</li>
<li data-id="2">Item 2</li>
<li data-id="3">Item 3</li>
</ul>
const list = document.getElementById('list');
// 不推荐:给每个 li 绑定事件
// document.querySelectorAll('li').forEach(li => {
// li.addEventListener('click', handler);
// });
// 推荐:事件委托
list.addEventListener('click', (e) => {
// 判断点击的是否是 li 元素
if (e.target.tagName === 'LI') {
const id = e.target.dataset.id;
console.log('点击了:', id);
}
});
// 更健壮的写法:处理点击 li 内部元素的情况
list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (li && list.contains(li)) {
console.log('点击了:', li.dataset.id);
}
});
阻止传播与默认行为
element.addEventListener('click', (e) => {
// 阻止事件继续传播(捕获或冒泡)
e.stopPropagation();
// 阻止同一元素上的其他监听器执行
e.stopImmediatePropagation();
// 阻止默认行为(如链接跳转、表单提交)
e.preventDefault();
});
完整示例
<!DOCTYPE html>
<html>
<body>
<div id="grandparent" style="padding: 50px; background: #f0f0f0;">
Grandparent
<div id="parent" style="padding: 50px; background: #ddd;">
Parent
<div id="child" style="padding: 50px; background: #bbb;">
Child
</div>
</div>
</div>
<script>
const elements = ['grandparent', 'parent', 'child'];
elements.forEach(id => {
const el = document.getElementById(id);
// 捕获阶段
el.addEventListener('click', (e) => {
console.log(`${id} 捕获, target: ${e.target.id}`);
}, true);
// 冒泡阶段
el.addEventListener('click', (e) => {
console.log(`${id} 冒泡, target: ${e.target.id}`);
});
});
// 点击 child 输出:
// grandparent 捕获, target: child
// parent 捕获, target: child
// child 捕获, target: child
// child 冒泡, target: child
// parent 冒泡, target: child
// grandparent 冒泡, target: child
</script>
</body>
</html>
关键点
- 事件流:捕获阶段 → 目标阶段 → 冒泡阶段
addEventListener第三个参数为true时在捕获阶段触发target是触发事件的元素,currentTarget是绑定监听器的元素- 事件委托利用冒泡,在父元素上处理子元素事件,减少内存占用,支持动态元素
stopPropagation()阻止传播,preventDefault()阻止默认行为
目录