一、事件冒泡
事件冒泡是指事件从最具体的元素开始触发,然后逐级向上传播到更一般的元素。简单来说,子到父/内到外。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 200px;
height: 200px;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="outer">
<div id="middle">
<div id="inner"></div>
</div>
</div>
<script>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
outer.addEventListener('click', () => {
console.log('Outer clicked');
},);
middle.addEventListener('click', () => {
console.log('Middle clicked');
},);
inner.addEventListener('click', () => {
console.log('Inner clicked');
},);
</script>
</body>
</html>
输出结果为:
Inner clicked
Middle clicked
Outer clicked
二、事件捕获
事件捕获与事件冒泡相反,它是从最顶层的元素开始,逐级向下传播到最具体的元素。简单来说,父到子/外到内。
<script>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
outer.addEventListener('click', () => {
console.log('Outer clicked');
}, true );
middle.addEventListener('click', () => {
console.log('Middle clicked');
}, true );
inner.addEventListener('click', () => {
console.log('Inner clicked');
}, true );
</script>
输出结果为:
Outer clicked
Middle clicked
Inner clicked
三、冒泡与捕获区别
为什么要同时使用捕获和冒泡功能?当浏览器的交叉兼容性远不如现在,Netscape 只使用事件捕捉,而 Internet Explorer 只使用事件冒泡。当 W3C 决定尝试将行为标准化并达成共识时,最终确定了这个包括这两种行为的系统。
| 类型 | 区别 |
|---|---|
| 冒泡 | 子到父/内到外 |
| 捕获 | 父到子/外到内 |
同时绑定事件捕获和冒泡,可以更直观对比触发顺序
<script>
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const inner = document.getElementById('inner');
outer.addEventListener('click', () => {
console.log('Outer clicked');
}, true);
middle.addEventListener('click', () => {
console.log('Middle clicked');
}, true);
inner.addEventListener('click', () => {
console.log('Inner clicked');
}, true);
outer.addEventListener('click', () => {
console.log('Outer clicked');
},);
middle.addEventListener('click', () => {
console.log('Middle clicked');
},);
inner.addEventListener('click', () => {
console.log('Inner clicked');
},);
</script>
输出结果为:
Outer clicked
Middle clicked
Inner clicked
Inner clicked
Middle clicked
Outer clicked
四、认知误区
addEventListener 的第三个参数 useCapture 容易误解为事件传播只有某个阶段;
其实事件传播分为3个阶段:
- capture phase:捕获阶段
- target phase:命中阶段
- bubble phase:冒泡阶段
useCapture 真正含义是决定在什么阶段触发。完整流程如图,