bubbling and capturing
Published by powerfulyang on Dec 22, 2020
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 2 3 4 5 6 7 8 9
<div class='parent' onclick='alert(`parent`)'> <div class='child' onclick='clickChild(event)'>child</div> </div> <script> function clickChild(e) { alert('child'); e.stopPropagation(); } </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
:
1 2 3
elem.addEventListener(..., {capture: true}) // 或者,用 {capture: true} 的别名 "true" elem.addEventListener(..., true)
bubbling vs capturing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<style> body * { margin: 10px; border: 1px solid blue; } </style> <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>
上面这段代码为文档中的 每个 元素都设置了点击处理程序,以查看哪些元素上的点击事件处理程序生效了。
如果你点击了 <p>
,那么顺序是:
HTML
→BODY
→FORM
→DIV
(捕获阶段第一个监听器):P
(目标阶段,触发两次,因为我们设置了两个监听器:捕获和冒泡)DIV
→FORM
→BODY
→HTML
(冒泡阶段,第二个监听器)。
每个处理程序都可以访问 event
对象的属性:
event.target
—— 引发事件的层级最深的元素。event.currentTarget
(=this
)—— 处理事件的当前元素(具有处理程序的元素)event.eventPhase
—— 当前阶段(capturing=1,target=2,bubbling=3)。