理解JavaScript中的事件冒泡、捕获和委托
事件冒泡
冒泡意味着事件从目标元素(即用户点击的button
)开始,沿着其祖先树向上传播。默认情况下,所有事件都会冒泡。
为了更好地理解事件冒泡,考虑以下HTML示例,我们将在本文的大部分内容中引用它:
<html>
<body>
<div id="btn-container">
<button class="btn">Click me</button>
</div>
</body>
</html>
const ancestors = [
window, document, document.documentElement,
document.body, document.getElementById('btn-container')
];
// 目标阶段
document.querySelector('.btn').addEventListener('click', e => {
console.log(`Hello from ${e.target}`);
});
// 冒泡阶段
ancestors.forEach(a => {
a.addEventListener('click', e => {
console.log(`Hello from ${e.currentTarget}`);
});
});
如果我们像上面的示例一样为树中的每个元素添加事件监听器,我们会看到首先由button
触发一个监听器,然后从最近的祖先一直冒泡到Window
的每个监听器都会触发。
事件捕获
捕获是冒泡的完全相反,意味着外部事件处理程序在最具体的处理程序(即button
上的处理程序)之前触发。请注意,所有捕获事件处理程序都会先运行,然后再运行所有冒泡事件处理程序。
你可以通过在EventTarget.addEventListener
中应用第三个参数并将其设置为true
来使用事件捕获。例如:
// 捕获阶段
ancestors.forEach(a => {
a.addEventListener('click', e => {
console.log(`Hello from ${e.currentTarget}`);
}, true);
});
给定这段代码,我们会看到每个祖先元素的监听器首先被触发,然后才会触发按钮的监听器。
事件传播
在解释事件冒泡和捕获之后,我们现在可以解释事件传播的三个阶段:
- 在捕获阶段,事件从
Window
开始,向下移动到Document
,然后通过目标元素的祖先元素。 - 在目标阶段,事件在事件目标上触发(例如用户点击的
button
)。 - 在冒泡阶段,事件从目标元素的祖先元素向上冒泡,直到根元素、
Document
,最后到达Window
。
事件委托
事件委托是指将事件监听委托给父元素,而不是直接在事件目标上添加事件监听器。使用这种技术,父元素可以捕获和处理冒泡事件。
window.addEventListener('click', e => {
if (e.target.className === 'btn') console.log('Hello there!');
});
在上面的示例中,我们将事件处理从button
委托给了Window
,并使用Event.target
来获取原始事件的目标元素。
使用事件委托模式有两个优点:
- 通过使用事件委托,我们可以在大量元素上监听事件,而无需逐个附加事件监听器,这可以提供性能上的优势。
- 通过使用事件委托,动态元素(即在DOM中随时间添加或删除的元素)可以捕获和处理其事件,而无需注册或删除监听器。