请看以下代码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box1{
width: 500px;
height: 500px;
background-color: red;
}
.box2{
width: 300px;
height: 300px;
background-color: green;
}
.box3{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
</head>
<body>
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
<script>
let box1 = document.querySelector('.box1');
let box2 = document.querySelector('.box2');
let box3 = document.querySelector('.box3');
box1.addEventListener('click',function(){
console.log('box1 red');
}, true)
box2.addEventListener('click',function(){
console.log('box2 green');
}, false)
box3.addEventListener('click',function(){
console.log('box3 blue');
}, false)
</script>
</body>
</html>
大家可以猜测以下,假设我点了蓝色方块,代码会怎么触发呢?
想要知道答案,我们需要先了解以下浏览器的事件流。
什么是事件流呢,在浏览器中,事件流(Event Flow) 是指 事件从触发到处理的传播过程。它描述了事件如何从文档树的根节点传递到目标元素,然后再从目标元素返回到根节点的过程。
一、事件流的三个阶段
事件流分为三个阶段,依次为:
-
捕获阶段(Capture Phase)
- 事件从
window开始,沿着 DOM 树向下传播,直到到达目标元素。 - 这个阶段通常用于提前拦截事件。
- 事件从
-
目标阶段(Target Phase)
- 事件到达目标元素,触发目标元素上的事件处理程序。
-
冒泡阶段(Bubble Phase)
- 事件从目标元素开始,沿着 DOM 树向上传播,直到回到
window。 - 这个阶段是最常用的事件处理阶段。
- 事件从目标元素开始,沿着 DOM 树向上传播,直到回到
二、事件流的传播过程
以下是一个典型的事件流传播过程:
- 捕获阶段:
window→document→<html>→<body>→ 目标元素的父元素。 - 目标阶段:目标元素。
- 冒泡阶段:目标元素的父元素 →
<body>→<html>→document→window。
三、事件流的代码示例
简单了解之后,我们来分析一下以下代码吧。
<div id="outer">
<div id="inner">点击我</div>
</div>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
// 捕获阶段
outer.addEventListener("click", () => {
console.log("捕获阶段:outer");
}, true); // 第三个参数为 true,表示在捕获阶段触发
// 目标阶段
inner.addEventListener("click", () => {
console.log("目标阶段:inner");
});
// 冒泡阶段
outer.addEventListener("click", () => {
console.log("冒泡阶段:outer");
}, false); // 第三个参数为 false(默认),表示在冒泡阶段触发
点击 inner 元素后的输出:
捕获阶段:outer
目标阶段:inner
冒泡阶段:outer
所有我们可以知道,当我们点击了‘点击我’,首先会捕获到outer,然后是inner,到达目标之后呢,在从inner返回到outer。
当我们不设置addEventListener的第三个参数,则默认false,在冒泡阶段触发,也就是先触发inner的点击事件,然后是outer的点击事件。
目标阶段:inner
冒泡阶段:outer
反之第三个参数设置为true,在捕获阶段触发。就会先触发outer的点击事件,然后inner是的点击事件。
捕获阶段:outer
目标阶段:inner
四、事件流的控制方法
-
阻止事件传播
- 使用
event.stopPropagation()阻止事件继续传播(捕获或冒泡)。 - 使用
event.stopImmediatePropagation()阻止事件传播,并阻止同一元素上的其他事件处理程序执行。
inner.addEventListener("click", (event) => { console.log("inner 点击"); event.stopPropagation(); // 阻止事件冒泡 }); - 使用
-
阻止默认行为
- 使用
event.preventDefault()阻止事件的默认行为(如表单提交、链接跳转)。
const link = document.querySelector("a"); link.addEventListener("click", (event) => { event.preventDefault(); // 阻止链接跳转 console.log("链接点击被阻止"); }); - 使用
五、事件委托(Event Delegation)
事件委托是一种利用事件冒泡机制的优化技术。将事件监听器绑定到父元素,通过事件冒泡处理子元素的事件。
1. 优点
- 减少事件监听器的数量,提升性能。
- 动态添加的子元素无需重新绑定事件。
2. 代码示例
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
const list = document.getElementById("list");
list.addEventListener("click", (event) => {
if (event.target.tagName === "LI") {
console.log("点击了:", event.target.textContent);
}
});
六、总结
- 事件流分为捕获、目标、冒泡三个阶段。
- 捕获阶段:从
window到目标元素。 - 目标阶段:事件到达目标元素。
- 冒泡阶段:从目标元素回到
window。
这下我们就能理解开头的代码返回的是什么了,大家可以在评论区讨论讨论!