前言
让我们从一个示例开始。
处理程序(handler)被分配给了 <div>
,但是如果你点击任何嵌套的标签(例如 <em>
或 <code>
),该处理程序也会运行:
<div onclick="alert('The handler!')">
<em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em>
</div>
So, if you click on EM, the handler on DIV runs.
冒泡
冒泡(bubbling)原理很简单。
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
假设我们有 3 层嵌套 FORM > DIV > P
,它们各自拥有一个处理程序:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
现在,如果你点击 <p>
,那么会发生什么?
首先,处理程序在
<p>
上运行。然后,它在
<div>
上运行。然后,它在
<form>
上运行。最后,它在 document 上运行。
这种行为被称为“事件冒泡”,因为它像气泡一样从元素冒出来。
因此,如果我们点击 <p>
,那么我们将看到 3 个 alert:p → div → form。
event.target
父元素上的处理程序始终可以获取事件实际发生位置的详细信息。
引发事件的那个嵌套层级最深的元素被称为目标元素,可以通过 event.target 访问。
注意与 this(=event.currentTarget)之间的区别:
this
是在处理程序运行时的“当前”元素,它始终相同。event.target
是在事件发生时的“目标”元素,它可以是任何元素,它在冒泡过程中改变。
那我们来举个例子吧!
例如,如果我们有一个处理程序 form.onclick,那么它可以“捕获”表单内的所有点击。无论点击发生在哪里,它都会冒泡到 <form>
并运行处理程序。
注意哈!这边是将所有的事件都绑定到了 form 上,而不是每个元素上。
在 form.onclick 处理程序中:
this(=event.currentTarget)是 <form>
元素,因为处理程序在它上面运行。
event.target 是表单中实际被点击的元素。
A click shows both event.target
and this
to compare:
停止冒泡
冒泡事件从目标元素开始向上冒泡。通常,它会一直上升到 <html>
,然后再到 document 对象,有些事件甚至会到达 window,它们会调用路径上所有的处理程序。
但是任意处理程序都可以决定事件已经被完全处理,并停止冒泡。
用于停止冒泡的方法是 event.stopPropagation()。
例如,如果你点击 <button>
,这里的 body.onclick 不会工作:
<body onclick="alert(`the bubbling doesn't reach here`)">
<button onclick="event.stopPropagation()">Click me</button>
</body>
捕获
事件处理的另一个阶段被称为“捕获(capturing)”。
DOM 事件标准描述了事件传播的 3 个阶段:
- 捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
- 目标阶段(Target phase)—— 事件到达目标元素。
- 冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡。
下面是在表格中点击 <td>
的图片,摘自规范:
也就是说:点击 <td>
,事件首先通过祖先链向下到达元素(捕获阶段),然后到达目标(目标阶段),最后上升(冒泡阶段),在途中调用处理程序。
之前,我们只讨论了冒泡,因为捕获阶段很少被使用。通常我们看不到它。
使用 on<event>
属性或使用 HTML 特性(attribute)或使用两个参数的 addEventListener(event, handler) 添加的处理程序,对捕获一无所知,它们仅在第二阶段和第三阶段运行。
总结
当一个事件发生时 —— 发生该事件的嵌套最深的元素被标记为“目标元素”(event.target)。
- 然后,事件从文档根节点向下移动到 event.target,并在途中调用分配了 addEventListener(…, true) 的处理程序(true 是 {capture: true} 的一个简写形式)。
- 然后,在目标元素自身上调用处理程序。
- 然后,事件从 event.target 冒泡到根,调用使用 on
<event>
、HTML 特性(attribute)和没有第三个参数的,或者第三个参数为 false/{capture:false} 的addEventListener 分配的处理程序。
每个处理程序都可以访问 event 对象的属性:
- event.target —— 引发事件的层级最深的元素。
- event.currentTarget(=this)—— 处理事件的当前元素(具有处理程序的元素)
- event.eventPhase —— 当前阶段(capturing=1,target=2,bubbling=3)。
任何事件处理程序都可以通过调用 event.stopPropagation() 来停止事件,但不建议这样做,因为我们不确定是否确实不需要冒泡上来的事件,也许是用于完全不同的事情。
捕获阶段很少使用,通常我们会在冒泡时处理事件。这背后有一个逻辑。
事件处理程序也是如此。在特定元素上设置处理程序的代码,了解有关该元素最详尽的信息。特定于
評論