bubbling and capturing
bubbling
-
stopPropagation
prevents further propagation of the current event in the capturing and bubbling phases.
如果一个元素在一个事件上有多个处理程序,即使其中一个停止冒泡,其他处理程序仍会执行。
换句话说,event.stopPropagation()
停止向上移动,但是当前元素上的其他处理程序都会继续运行。
有一个event.stopImmediatePropagation()
方法,可以用于停止冒泡,并阻止当前元素上的处理程序运行。使用该方法之后,其他处理程序就不会被执行。 -
preventDefault
prevents the default action the browser makes on that event. -
使用 css
pointer-events: none
实现点击穿透
1 <div class='parent' onclick='alert(`parent`)'>
2 <div class='child' onclick='clickChild(event)'>child</div>
3 </div>
4 <script>
5 function clickChild(e) {
6 alert('child');
7 e.stopPropagation();
8 }
9 </script>
stopImmediatePropagation
如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。 如果在其中一个事件监听器中执行stopImmediatePropagation()
,那么剩下的事件监听器都不会被调用,还会阻止冒泡。
不必要的 stopPropagation
不要在没有需要的情况下停止冒泡!
冒泡很方便。不要在没有真实需求时阻止它:除非是显而易见的,并且在架构上经过深思熟虑的。
有时 event.stopPropagation()
会产生隐藏的陷阱,以后可能会成为问题。
例如:
- 我们创建了一个嵌套菜单,每个子菜单各自处理对自己的元素的点击事件,并调用
stopPropagation
,以便不会触发外部菜单。 - 之后,我们决定捕获在整个窗口上的点击,以追踪用户的行为(用户点击的位置)。有些分析系统会这样做。通常,代码会使用
document.addEventListener('click'…)
来捕获所有的点击。 - 我们的分析不适用于被
stopPropagation
所阻止点击的区域。太伤心了,我们有一个“死区”。
通常,没有真正的必要去阻止冒泡。一项看似需要阻止冒泡的任务,可以通过其他方法解决。其中之一就是使用自定义事件,稍后我们会介绍它们此外,我们还可以将我们的数据写入一个处理程序中的 event
对象,并在另一个处理程序中读取该数据,这样我们就可以向父处理程序传递有关下层处理程序的信息。
capturing
DOM 事件标准描述了事件传播的 3 个阶段:
- 捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
- 目标阶段(Target phase)—— 事件到达目标元素。
- 冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡。
使用 on<event>
属性或使用 HTML 特性(attribute)或使用两个参数的 addEventListener(event, handler)
添加的处理程序,对捕获一无所知,它们仅在第二阶段和第三阶段运行。
为了在捕获阶段捕获事件,我们需要将处理程序的 capture
选项设置为 true
:
1elem.addEventListener(..., {capture: true})
2// 或者,用 {capture: true} 的别名 "true"
3elem.addEventListener(..., true)
bubbling vs capturing
1<style>
2 body * {
3 margin: 10px;
4 border: 1px solid blue;
5 }
6</style>
7
8<form>FORM
9 <div>DIV
10 <p>P</p>
11 </div>
12</form>
13
14<script>
15 for(let elem of document.querySelectorAll('*')) {
16 elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
17 elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
18 }
19</script>
上面这段代码为文档中的 每个 元素都设置了点击处理程序,以查看哪些元素上的点击事件处理程序生效了。
如果你点击了 <p>
,那么顺序是:
HTML
→BODY
→FORM
→DIV
(捕获阶段第一个监听器):P
(目标阶段,触发两次,因为我们设置了两个监听器:捕获和冒泡)DIV
→FORM
→BODY
→HTML
(冒泡阶段,第二个监听器)。
每个处理程序都可以访问 event
对象的属性:
event.target
—— 引发事件的层级最深的元素。event.currentTarget
(=this
)—— 处理事件的当前元素(具有处理程序的元素)event.eventPhase
—— 当前阶段(capturing=1,target=2,bubbling=3)。