DOM 基础
1、 DOM 事件模型:冒泡和捕获
1.1、 冒泡
冒泡(bubbling)原理很简单。
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。
假设我们有 3 层嵌套 FORM > DIV > P,它们各自拥有一个处理程序:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="console.log('form')">FORM
<div onclick="console.log('div')">DIV
<p onclick="console.log('p')">P</p>
</div>
</form>
点击内部的 <p> 会首先运行 onclick:
- 在该
<p>上的。 - 然后是外部
<div>上的。 - 然后是外部
<form>上的。 - 以此类推,直到最后的 document 对象。

1.2、 不冒泡的事件
focus 事件不会冒泡。同样,我们以后还会遇到其他例子。但这仍然是例外,而不是规则,大多数事件的确都是冒泡的。
1.3、 停止冒泡
冒泡事件从目标元素开始向上冒泡。通常,它会一直上升到 <html>,然后再到 document 对象,有些事件甚至会到达 window,它们会调用路径上所有的处理程序。
但是任意处理程序都可以决定事件已经被完全处理,并停止冒泡。
用于停止冒泡的方法是 event.stopPropagation()。
例如,如果你点击 <button>,这里的 body.onclick 不会工作:
立即停止冒泡
event.stopImmediatePropagation()
如果一个元素在一个事件上有多个处理程序,即使其中一个停止冒泡,其他处理程序仍会执行。
换句话说,event.stopPropagation() 停止向上移动,但是当前元素上的其他处理程序都会继续运行。
有一个 event.stopImmediatePropagation() 方法,可以用于停止冒泡,并阻止当前元素上的处理程序运行。使用该方法之后,其他处理程序就不会被执行。
不要在没有需要的情况下停止冒泡!
冒泡很方便。不要在没有真实需求时阻止它:除非是显而易见的,并且在架构上经过深思熟虑的。
有时 event.stopPropagation() 会产生隐藏的陷阱,以后可能会成为问题。
例如:
我们创建了一个嵌套菜单,每个子菜单各自处理对自己的元素的点击事件,并调用 stopPropagation,以便不会触发外部菜单。
之后,我们决定捕获在整个窗口上的点击,以追踪用户的行为(用户点击的位置)。有些分析系统会这样做。通常,代码会使用 document.addEventListener('click'…) 来捕获所有的点击。
我们的分析不适用于被 stopPropagation 所阻止点击的区域。太伤心了,我们有一个“死区”。
通常,没有真正的必要去阻止冒泡。一项看似需要阻止冒泡的任务,可以通过其他方法解决。其中之一就是使用自定义事件,稍后我们会介绍它们此外,我们还可以将我们的数据写入一个处理程序中的 event 对象,并在另一个处理程序中读取该数据,这样我们就可以向父处理程序传递有关下层处理程序的信息。
1.4、 捕获
事件处理的另一个阶段被称为“捕获(capturing)”。它很少被用在实际开发中,但有时是有用的。
DOM 事件标准描述了事件传播的 3 个阶段:
捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
目标阶段(Target phase)—— 事件到达目标元素。
冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡。
下面是在表格中点击 <td> 的图片,摘自规范:

为了在捕获阶段捕获事件,我们需要将处理程序的 capture 选项设置为 true:
elem.addEventListener(..., {capture: true})
// 或者,用 {capture: true} 的别名 "true"
elem.addEventListener(..., true)