我正在参加「掘金·启航计划」
前言
DOM事件流是Web开发中一个重要的概念,它描述了事件在HTML文档结构中传播的方式。理解DOM事件流对于正确处理和管理事件至关重要。本文将介绍DOM事件流的两个关键概念:事件捕获和事件冒泡,并解释它们在事件处理过程中的作用和应用。
什么是DOM事件流?
DOM事件流是指事件在DOM结构中传播的顺序。当一个事件在特定元素上触发时,它首先经历事件捕获阶段,然后是目标阶段,最后是事件冒泡阶段。理解这个过程对于理解事件处理的工作原理至关重要。多说无益,上图:
addEventListener方法
EventTarget.addEventListener()
用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);
该方法接受三个参数。
type
:事件名称,大小写敏感。listener
:监听函数。事件发生时,会调用该监听函数。useCapture
:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。
事件冒泡
事件冒泡阶段是指事件从目标元素开始向上传播,直到达到文档根节点。在事件冒泡阶段,事件会依次经过目标元素的每个祖先元素。这意味着目标元素首先处理事件,然后是它的父元素,以此类推,直到达到最外层的元素。类似于一个气泡,从水底冒出来,逐渐变大,直到水面。
举个栗子
<!DOCTYPE html>
<html>
<head>
<title>事件冒泡示例</title>
</head>
<body>
<div class="outer">
<div class="inner">
<button id="myButton">点击我</button>
</div>
</div>
<script>
var myButton = document.getElementById('myButton');
var outerDiv = document.querySelector('.outer');
var innerDiv = document.querySelector('.inner');
myButton.addEventListener('click', function(event) {
console.log('按钮被点击了!');
console.log('事件目标:', event.target);
console.log('当前元素:', this);
console.log('冒泡阶段:按钮 -> 内部div -> 外部div -> body -> html -> document');
});
outerDiv.addEventListener('click', function(event) {
console.log('外部div被点击了!');
});
innerDiv.addEventListener('click', function(event) {
console.log('内部div被点击了!');
});
</script>
</body>
</html>
在控制台会输出以下内容:
按钮被点击了!
事件目标: <button id="myButton">点击我</button>
当前元素: <button id="myButton">点击我</button>
冒泡阶段:按钮 -> 内部div -> 外部div -> body -> html -> document
内部div被点击了!
外部div被点击了!
事件捕获
事件捕获阶段是指事件从文档根节点向下传播,直到达到实际触发事件的元素。在事件捕获阶段,事件会依次经过每个祖先元素。这意味着最外层的元素首先接收到事件,然后是它的父元素,以此类推,直到达到目标元素。
举个栗子
将上面的栗子中的useCapture
设置为true
,即可实现事件捕获。
此时控制台将输出以下内容:
外部div被点击了!
内部div被点击了!
按钮被点击了!
事件目标: <button id="myButton">点击我</button>
当前元素: <button id="myButton">点击我</button>
冒泡阶段:按钮 -> 内部div -> 外部div -> body -> html -> document
事件捕获和事件冒泡的应用
- 事件代理:通过将事件处理程序附加到祖先元素上,我们可以利用事件冒泡机制,在一个元素的父元素上处理其子元素的事件。这样可以减少事件处理程序的数量,并且在动态添加或删除元素时更加方便。
<!DOCTYPE html>
<html>
<head>
<title>事件代理示例</title>
</head>
<body>
<ul id="myList">
<li>
<button>按钮 1</button>
</li>
<li>
<button>按钮 2</button>
</li>
<li>
<button>按钮 3</button>
</li>
</ul>
<script>
var myList = document.getElementById('myList');
myList.addEventListener('click', function(event) {
if (event.target.tagName === 'BUTTON') {
var buttonText = event.target.innerText;
console.log('按钮 "' + buttonText + '" 被点击了!');
}
});
</script>
</body>
</html>
依次点击三个按钮将输出以下内容
按钮 "按钮 1" 被点击了!
按钮 "按钮 2" 被点击了!
按钮 "按钮 3" 被点击了!
在上述示例中,我们有一个包含多个列表项的无序列表。每个列表项中都有一个按钮。通过事件代理,我们在父元素myList
上添加了一个点击事件监听器。当任何一个按钮被点击时,事件都会冒泡到父元素,然后通过判断事件目标的标签名是否为BUTTON
,我们可以确定点击事件来自按钮元素。然后,我们可以获取按钮的文本内容并进行相应的处理。
- 事件委托:事件委托是一种利用事件冒泡机制的技术,可以将事件处理程序绑定到静态父元素上,从而处理动态生成的子元素的事件。这对于性能优化和代码简化非常有用。
<!DOCTYPE html>
<html>
<head>
<title>事件委托示例</title>
</head>
<body>
<div id="buttonContainer" class="button-list"></div>
<script>
var buttonContainer = document.getElementById('buttonContainer');
// 模拟动态生成按钮
for (var i = 1; i <= 10; i++) {
var button = document.createElement('button');
button.textContent = '按钮 ' + i;
button.className = 'button';
buttonContainer.appendChild(button);
}
buttonContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('button')) {
var buttonText = event.target.innerText;
console.log('按钮 "' + buttonText + '" 被点击了!');
}
});
</script>
</body>
</html>
运行得到以下结果:
在上述示例中,我们动态生成了一个按钮列表,并将按钮添加到父元素buttonContainer
中。通过事件委托,我们在父元素上添加了一个点击事件监听器。当任何一个按钮被点击时,事件都会冒泡到父元素,然后通过判断事件目标的类名是否包含button,我们可以确定点击事件来自按钮元素。然后,我们可以获取按钮的文本内容并进行相应的处理。
-
阻止事件传播:通过在事件处理程序中使用
event.stopPropagation()
方法,我们可以阻止事件在DOM树中的进一步传播,即停止事件的事件捕获和事件冒泡阶段。 -
处理事件冲突:当多个元素重叠时,可能会发生事件冲突。事件捕获和事件冒泡机制允许我们更灵活地处理这些冲突。通过在适当的阶段上处理事件,我们可以决定哪个元素优先处理事件,从而避免冲突和不一致的行为。