Skip to content

理解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中随时间添加或删除的元素)可以捕获和处理其事件,而无需注册或删除监听器。