开篇
stopImmediatePropagation
与stopPropagation
是一对亲兄弟,他们两实现的功能是相似的,那么这两个函数的区别在哪里呢?如果你对于事件传播已经很了解,那么可以直接跳到总结部分,反之可以按照顺序阅读。
什么是DOM事件流?
早上起床你都会做些什么呢?闹钟响了,你摸索着掐掉闹钟,再眯一会,然后起床,刷牙,吃早餐……..,这一系列事件连接起来就构成了你起床的事件流。而DOM事件也是有一个流程的,DOM 事件标准描述了事件传播的 3 个阶段:
- 捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
- 目标阶段(Target phase)—— 事件到达目标元素。
- 冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡。
事件捕获
DOM事件触发时(被触发DOM事件的这个元素被叫作事件源),浏览器会从根节点开始 由外到内 进行事件传播。即事件从文档的根节点流向目标对象节点,途中经过各个层次的DOM节点,最终到目标节点,完成事件捕获,事件冒泡
事件冒泡与事件捕获顺序相反。事件捕获的顺序是从外到内,事件冒泡是从内到外。
当事件传播到了目标阶段后,处于目标阶段的元素就会将接收到的时间向上传播,就是顺着事件捕获的路径,反着传播一次,逐级的向上传播到该元素的祖先元素,直到window对象。引发事件的那个嵌套层级最深的元素被称为目标元素,可以通过
event.target
访问。注意与this
(=event.currentTarget
)之间的区别:-
event.target
—— 是引发事件的“目标”元素,它在冒泡过程中不会发生变化。 -
this
—— 是“当前”元素,其中有一个当前正在运行的处理程序。
使用addEventListener监听事件
使用EventTarget.addEventListeners
可以指定的侦听器注册到EventTarget上,我们可以监听页面上发生的诸多事件(mousedown
,click
,scroll
等),当事件发生时就可以调用相应的函数进行事件处理。 -
它接收三个参数:分别是type
, listener
, options
, 其中type
表示监听事件类型的字符串,listener
通常传入的是一个函数,在相应事件发生时调用它进行事件处理,options
是一个与listener
有关的可选参数对象,可用的参数有capture
,once
,passive
, signal
,
在本文中比较关注的是capture
, 默认为false
, 代表该侦听器在时间冒泡阶段触发,反之在事件捕获阶段触发。
如何阻止事件传播?
很经典的一个问题是如何阻止事件冒泡,很多人应该马上就能想到使用event.cancleBuble = true
或者使用event.stopPropagation()
, 这的确能够阻止事件冒泡。其实更准确的说,应该是阻止了事件传播, 因为这两者不仅仅可以在冒泡阶段使用,也可以在捕获阶段使用。看下面这一个例子。
Event.cancelBubble
属性是Event.stopPropagation()
的一个曾用名。在从事件处理程序返回之前将其值设置为true可阻止事件的传播。
dom结构如下:
1 | <div class="A" style="font-size:30px"> |
script部分:
1 | const p = document.querySelector('p') |
上述事件传播的路径是:A -> B -> P -> B -> A, 选定<p></p>
作为目标元素,每次试验时点击图中的笑脸所在的绿色区域。
我们在P元素上绑定了两个事件侦听器,并使用了event.cancleBuble = true
(event.stopPropagation()
)成功阻止了事件冒泡。如果我在事件捕获阶段使用会怎么样呢?
1 | const p = document.querySelector('p') |
从结果可以清楚地看到成功地阻止了事件传播。
那么现在让我们回到最初的问题:stopImmediatePropagation与stopPropagation有什么区别?
可以这样简单第理解:stopImmediatePropagation
是stopPropagation
的加强版,前者不仅仅可以阻止事件传播,它还可以在多个事件监听器被附加到相同元素的相同事件类型上时保证只有一个事件侦听器被调用。换句话说,当同一个元素上有多个事件侦听器侦听同一事件时,这些事件侦听器都会被按照初始添加顺序被调用,如上文中p元素就绑定了两个监听mousedown
事件的侦听器;但如果在其中一个事件侦听器中使用了event.stopImmediatePropagation()
的话,在该侦听器之后的侦听器就不会被调用了。
将代码改造成如下:
1 | const p = document.querySelector('p') |
可以看到stopImmediatePropagation
成功阻止了事件冒泡,A元素,B元素的侦听器都没有被调用,此外元素p上绑定了四个侦听器,其中三个侦听mousedown
事件,剩下一个侦听click
事件,我们在第二个侦听器中调用stopImmediatePropagation
,前两个侦听器顺利调用,此外,由于最后一个侦听器侦听click
事件,不受影响。
总结
- 事件传播: 捕获 -> 目标元素 -> 冒泡
event
event.target
—— 引发事件的层级最深的元素。event.currentTarget
(=this
)—— 处理事件的当前元素(具有处理程序的元素)event.eventPhase
—— 当前阶段(capturing=1,target=2,bubbling=3)。
- 阻止事件传播的方法:
- event.stopPropagation()
- event.cancelBubble = true
- event.stopImmediatePropagation()
event.stopImmediatePropagation()
- 可以阻止事件传播
- 多个事件侦听器侦听同一元素的同一事件时,执行
event.stopImmediatePropagation()
的侦听器之后的侦听器被忽略 - 侦听同一元素的不同事件的侦听器不受影响
最好自己动手试一试,加深理解。
源代码在此处
参考
Event - MDN