JavaScript 事件冒泡和捕捉
在JavaScript中,事件的传播是通过’事件流’来实现的。事件流是特定网页接收事件的顺序。因此,在JS中,事件流的处理取决于三个方面:
事件捕捉
事件目标
事件冒泡
在这一部分中,我们将学习并讨论两个方面,即 事件冒泡 和 事件捕捉 。我们将逐个实际学习这些概念。
事件冒泡
在使用JavaScript开发网页或网站时,使用事件冒泡的概念,当一个元素嵌套在另一个元素上并且是相同事件的一部分时,事件处理程序被调用。这种技术或方法被称为事件冒泡。因此,在执行网页的事件流时,使用事件冒泡。我们可以理解事件冒泡为当一个元素嵌套在另一个元素中时,从最深的元素开始,沿着其父元素并一直到最上层的所有祖先元素,执行调用。
事件冒泡的示例
让我们来看下面的示例,以了解事件冒泡的工作原理:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Event Bubbling</title>
</head>
<body>
<div id="p1">
<button id="c1">I am child button</button>
</div>
<script>
var parent = document.querySelector('#p1');
parent.addEventListener('click', function(){
console.log("Parent is invoked");
});
var child = document.querySelector('#c1');
child.addEventListener('click', function(){
console.log("Child is invoked");
});
</script>
</body>
</html>
输出:
代码解释:
- 上面的代码是基于HTML和JavaScript的代码。
- 我们使用了一个具有id为p1的div标签,并在其中嵌套了一个具有id为c1的按钮。
- 现在,在JavaScript部分,我们使用querySelector()函数将html元素(p1和c1)分配给变量parent和child。
- 然后,我们创建并包含了一个事件,即点击事件,分别给div元素和子按钮创建了两个函数,这些函数将帮助我们知道父元素和子元素执行顺序的顺序。如果先调用子元素事件,将打印”child is invoked”,否则将打印”parent is invoked”。
- 因此,当按钮被点击时,它将首先打印”child is invoked”,这意味着子元素事件处理程序中的函数首先执行,然后转到调用div父函数。
这个顺序是由于事件冒泡的概念导致的。因此,事件冒泡就是这样发生的。
我们还可以通过下面的流程图来理解事件的流程:
这意味着当用户点击按钮时,点击事件从底部到顶部依次流动。
停止冒泡
从目标开始,向上移动是冒泡的过程,即从子级到父级,沿直线向上移动。但是,处理程序也可以在事件完全处理完之后决定停止冒泡。在JavaScript中,我们使用 event.stopPropagation() 方法。
例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Event Bubbling</title>
</head>
<body>
<div id="p1">
<button id="c1" onclick="event.stopPropagation()">I am child</button>
</div>
<script>
var parent = document.querySelector('#p1');
parent.addEventListener('click', function(){
console.log("Parent is invoked");
});
var child = document.querySelector('#c1');
child.addEventListener('click', function(){
console.log("Child is invoked");
});
</script>
</body>
</html>
在上述的代码中,当我们点击按钮时,它不会起作用,这是因为在这里调用了event.stopPropagation()方法,导致父函数不会被调用。
注意:event.stopPropagation()方法仅停止事件冒泡(在一个事件上),但所有其他处理程序仍在当前元素上运行。
为了停止冒泡并阻止处理程序在当前元素上运行,我们可以使用 event.stopImmediatePropagation() 方法。这是另一种停止冒泡和执行所有其他处理程序的方法。这意味着如果一个元素在一个事件上有多个事件处理程序,使用这个event.stopImmedaitePropagation()方法将停止所有事件处理程序的冒泡。
不要不必要地使用事件冒泡
尽管事件冒泡是一种方便的方法,但建议不必要地使用它。这是因为event.stopPropagation()方法会隐藏一些潜在的问题,这些问题可能在后期导致一些问题。
让我们通过一个示例来理解:
- 创建一个嵌套菜单,其中每个子菜单处理其元素上的点击事件,并通过调用event.stopPropagation()方法来阻止外部菜单的触发。
- 现在为了跟踪用户在点击时的行为,我们决定捕获整个窗口的点击事件,使用document.addEventListener(‘click’)。
- 但是由于我们调用了event.stopPropagation()方法,当点击被停止时,我们的分析将不会处理任何东西,因此我们得到了一个死区。
虽然没有了解事件捕获,事件冒泡的概念是不完整的,因此让我们从事件捕获开始,尝试结合这两个概念,完全理解概念和工作原理。
事件捕获
Netscape浏览器是第一个引入事件捕获概念的浏览器。事件捕获与事件冒泡相反,事件捕获中,事件从最外层元素向目标元素移动。而在事件冒泡的情况下,事件的移动从目标元素开始,向文件中最外层的元素移动。事件捕获在事件冒泡之前执行,但很少使用捕获,因为事件冒泡足以处理事件流。
事件捕获示例
让我们看一个示例代码来理解事件捕获的工作原理:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Event Capturing</title>
</head>
<body>
<div id="p1">
<button id="c1">I am Child</button>
</div>
<script>
var parent = document.querySelector('#p1');
var child = document.querySelector('#c1');
parent.addEventListener('click', function(){
console.log("Parent is invoked");
},true);
child.addEventListener('click', function(){
console.log("Child is invoked");
});
</script>
</body>
</html>
输出:
代码解释:
- 上述代码基于HTML和JavaScript。
- 在HTML部分,我们创建了一个id为p1的div。在div内部,我们嵌套并创建了一个id为c1的按钮。
- 在JS代码中,我们最初使用querySelector()方法将html元素即p1 id分配给变量parent,同样我们也将c1 id分配给变量child。
- 然后我们使用click事件并将其附加到p1 div和c1按钮上。同时包含一个在控制台上打印适当消息的函数。这意味着,如果首先调用的是子元素事件,则会首先在控制台上打印”Child is invoked”消息;而如果首先调用的是父元素事件处理程序,则会首先打印”Parent is invoked”消息。
- 接下来,我们在addEventListner()的第三个参数中为true,以启用在父div中进行事件捕获。
- 当我们单击按钮时,首先执行附加在父div中的函数。
- 随后,按钮的onclick()函数运行,这是由于事件捕获。由于事件捕获,父元素的事件首先执行,然后才执行目标元素的事件。
所以,当我们点击按钮时,点击事件按照以下顺序执行,如下面的流程图所示:
事件流程的完整概念
下图展示了事件流程的执行过程:
因此,事件处理和事件捕获都是事件委托的基础。这是事件流的绝对强大。