一、事件流是什么?
事件流就像你点击网页按钮时,浏览器处理点击事件的 “传播路径”。它决定了事件从触发到结束的完整过程,分为三个阶段:捕获阶段 → 目标阶段 → 冒泡阶段。
二、举个现实例子 🌰
想象你网购了一个快递:
捕获阶段:快递从北京仓库(顶层)出发,层层向下分发到省 → 市 → 区 → 街道(你的家)。
目标阶段:快递员敲你家门(触发事件的目标元素)。
冒泡阶段:你签收后,快递信息反向向上通知:街道 → 区 → 市 → 省 → 仓库(顶层)。
这就是事件流的完整过程!
三、事件流的三个阶段
1. 捕获阶段(Capture Phase)
方向:从最外层父元素(如 window)向目标元素 层层向下传递。
作用:类似快递从总部派送到你家,可以中途拦截事件。
代码控制:通过 addEventListener 的第三个参数设为 true 监听捕获阶段:
parent.addEventListener('click', function() {
console.log('捕获阶段:父元素被触发');
}, true); // true 表示监听捕获阶段
2. 目标阶段(Target Phase)
作用:事件到达你实际点击的元素(如按钮)。
细节:事件在目标元素上触发,无论是冒泡还是捕获阶段注册的监听器都会执行。
3. 冒泡阶段(Bubble Phase)
方向:从目标元素向上 层层冒泡 到最外层父元素。
作用:类似快递签收后,信息回传到总部。
默认监听:addEventListener 默认监听冒泡阶段:
child.addEventListener('click', function() {
console.log('冒泡阶段:子元素被触发');
}); // 第三个参数默认是 false(监听冒泡)
四、实际代码演示
假设 HTML 结构如下:
当你点击按钮时,事件流的执行顺序是:
捕获阶段:爷爷 → 爸爸 → 儿子
目标阶段:儿子
冒泡阶段:儿子 → 爸爸 → 爷爷
1.示例代码1
外层
中层
内层
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
// 捕获阶段触发
outer.addEventListener('click', function () {
console.log('外层捕获');
}, true);
middle.addEventListener('click', function () {
console.log('中层捕获');
}, true);
inner.addEventListener('click', function () {
console.log('内层捕获');
}, true);
// 冒泡阶段触发
outer.addEventListener('click', function () {
console.log('外层冒泡');
}, false);
middle.addEventListener('click', function () {
console.log('中层冒泡');
}, false);
inner.addEventListener('click', function () {
console.log('内层冒泡');
}, false);
2.示例代码2
爷爷
爸爸
儿子
const 爷爷 = document.querySelector('.爷爷');
const 爸爸 = document.querySelector('.爸爸');
const 儿子 = document.querySelector('.儿子');
// 监听捕获阶段(爷爷 → 爸爸 → 儿子)
爷爷.addEventListener('click', () => console.log('捕获阶段:爷爷'), true);
爸爸.addEventListener('click', () => console.log('捕获阶段:爸爸'), true);
儿子.addEventListener('click', () => console.log('捕获阶段:儿子'), true);
// 监听冒泡阶段(儿子 → 爸爸 → 爷爷)
爷爷.addEventListener('click', () => console.log('冒泡阶段:爷爷'));
爸爸.addEventListener('click', () => console.log('冒泡阶段:爸爸'));
儿子.addEventListener('click', () => console.log('冒泡阶段:儿子'));
五、事件流的实际应用
1. 事件委托(Event Delegation)
场景:动态添加的子元素(如列表项)需要绑定事件。
原理:利用冒泡阶段,直接在父元素上监听事件。
⑴.通俗理解
想象你开了一家超市,有很多货架(子元素)。如果每个货架都要安排一个员工(事件监听器)来处理顾客的咨询,会很浪费人力(内存)。
事件委托就像让一个经理(父元素)站在入口统一处理所有货架的咨询。当顾客(事件)在某个货架咨询时,问题会 “冒泡” 到经理那里,经理根据具体货架(event.target)来处理问题。这样既节省人力,又方便管理新添加的货架。
⑵.核心原理
事件冒泡:子元素触发事件后,事件会逐级向上传递到父元素。判断目标:父元素通过 event.target 找到真正触发事件的子元素,从而执行对应的操作。
⑶.代码示例解析
- 列表项 1
- 列表项 2
- 列表项 3
const list = document.getElementById('list'); // 获取父元素(ul)
// 在父元素上监听点击事件
list.addEventListener('click', function (event) {
// 检查点击的元素是否是
if (event.target.tagName === 'LI') {
console.log('你点击了列表项: ', event.target.textContent);
}
});
// 点击按钮动态添加新的
document.getElementById('addItem').addEventListener('click', function () {
const newItem = document.createElement('li');
newItem.textContent = '新列表项';
list.appendChild(newItem); // 新
});
父元素监听事件:
我们只给 ul(父元素)绑定了一个点击事件监听器,而不是给每个 li(子元素)绑定。
通过 event.target 判断目标:
当点击某个 li 时,事件会冒泡到 ul。event.target 会指向被点击的 li,因此 if (event.target.tagName === 'LI') 能精准识别点击的是哪个 li。
动态添加元素自动支持:
点击 “添加列表项” 按钮时,新的 li 会被添加到 ul 中。由于 ul 已经监听了点击事件,新的 li 无需额外绑定事件,自动生效。
⑷.事件委托的优点
节省内存:只需给父元素绑定一次事件,避免为每个子元素单独绑定。动态元素兼容:新增的子元素自动继承父元素的事件监听,无需重新绑定。简化代码:集中处理同类元素的事件,代码更简洁。
2.阻止事件传播
有时候,我们希望某个元素的事件不会影响到其父元素,这时可以使用 stopPropagation() 方法来阻止事件冒泡。
外层元素
内层元素
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
outer.addEventListener('click', function () {
console.log('外层元素被点击');
});
inner.addEventListener('click', function (event) {
event.stopPropagation();
console.log('内层元素被点击,但事件不会冒泡到外层元素');
});
3. 阻止默认行为
方法:e.preventDefault() 阻止元素的默认行为(如链接跳转)。
const link = document.getElementById('myLink');
link.addEventListener('click', function (e) {
e.preventDefault();
console.log('链接的默认跳转行为已被阻止');
});
六、总结
事件流三阶段:捕获 → 目标 → 冒泡(像快递的派送和回传)。
控制监听阶段:通过 addEventListener 的第三个参数选择监听捕获或冒泡。
灵活应用:事件委托、阻止传播等技巧能大幅简化代码逻辑。