JavaScript事件冒泡与事件捕获

JavaScript中事件传播的过程被称为“事件流”,最常用的术语是事件冒泡和事件捕获。通常事件流由以下三个概念构成:事件捕获,事件目标和事件冒泡。

在开始解释冒泡的概念之前,让我们参考一个案例。

在下面的例子中,事件处理程序被绑定到DIV元素,但任何嵌套的标签的点击时,都会触发响应程序。学习完冒泡之后,您会更好地理解这一点。

<!DOCTYPE html>
<html>
    <head>
        <title>Title of the Document</title>
    </head>
    <body>
        <div onclick="alert('Welcome to RUNOON')">
            <em>Click on <code>EM</code>, the handler on <code>DIV</code> starts.</em>
        </div>
    </body>
</html>

效果展示:

Click on EM, the handler on DIV starts.

事件冒泡

事件冒泡的原理很简单。每当某个元素触发事件时,首先,系统会在该元素上运行响应程序,然后在其父级元素上运行响应程序,再然后在其他祖先元素上依次运行响应程序。想象一下如果有3个嵌套元素 FORM>DIV>P ,每个元素都有一个处理程序,它们将如何触发响应事件?

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      body * {
        margin: 20px;
        border: 1px solid green;
      }
    </style>
  </head>
  <body>
    <form onclick="alert('form')">
      FORM
      <div onclick="alert('div')">
        DIV
        <p onclick="alert('p')">P</p>
      </div>
    </form>
  </body>
</html>

亲自试试

如果您单击内部P元素,则P元素上的onclick事件将首先运行,响应顺序如下:

  1. 首先,事件在元素P上开始响应。
  2. 其次,在外部DIV上开始响应。
  3. 第三,在外部FORM上开始响应。
  4. 然后向上直到文档对象。

因此,在您单击时P时,有3条警报。事件响应顺序从P->DIV->FORM。上面的过程称为冒泡,因为事件从内部元素传递父对象,就像水中的气泡一样向上逐层传递。


停止冒泡

通常,冒泡向上上升到<html>,然后上升到Document象。某些事件甚至可以到达window对象和调用处理程序。当事件已被完全处理时,处理程序都可以决定是否停止冒泡。我们可以使用event.stopPropagation()方法停止事件冒泡。

如非必要,不要轻易停止冒泡,有时event.stopPropagation()会产生隐藏的陷阱,引起一些不必要的问题。


事件捕捉

事件处理的另一个阶段称为事件捕获。实际上它很少使用,有时可能有用。事件捕获与冒泡相反,是由外而内展开的。最外面的元素先捕获事件,然后事件传播到内部元素。

在本章中,我们更加关注冒泡,因为捕获并不经常使用,并且通常对我们是不可见的。

为了在展示捕获事件,必须将响应程序捕获参数设置为true,如以下示例所示:

elem.addEventListener(..., {
  capture: true
})
// or, just "true" is an alias to {capture: true}
elem.addEventListener(..., true)

捕获参数有两个选项:

  • 如果为假,则意味着在冒泡阶段设置响应程序。
  • 如果为真,则在捕获阶段设置响应程序。

下面示例展示了事件冒泡和事件捕获的动作:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the Document</title>
    <style>
      body * {
        margin: 20px;
        border: 1px solid green;
      }
    </style>
  </head>
  <body>
    <form>
      FORM
      <div>
        DIV
        <p>P</p>
      </div>
    </form>
    <script>
      for(let elem of document.querySelectorAll('*')) {
        elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
        elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
      }
    </script>
  </body>
</html>

亲自试试

此外,还有一个特定的属性event.eventPhase,该属性返回捕获事件的编号。此属性也很少使用,通常你可以在响应程序中了解该属性。

相关