这两天仔细看了一下MDN上关于事件流机制和相关方法的文档,发现有个很大的误区。过去我一直觉得stopPropagation()就是用来阻止事件冒泡的,甚至不少博客和菜鸟教程上都是这样写的。但实际上文档中对stopPropagation()的解释是:函数
The
stopPropagation()
method of theEvent
interface prevents further propagation of the current event in the capturing and bubbling phasesspa
阻止当前事件在捕获和冒泡阶段的进一步传播,个人理解就是阻止当前事件的继续传播,不只阻止冒泡,也阻止捕获和目标阶段,只在于你是在哪一个阶段调用stopPropagation()的。code
一个事件被触发,它的事件流包括三个阶段:捕获、目标、冒泡。捕获阶段的传播路径是:window对象→document→HTML→body→...→target的直接父元素,从最外层最不具体的元素层层传递到最里层具体的元素。目标阶段就是事件目标元素上发生。冒泡阶段与捕获阶段的传播路径正好相反:target→...→body→HTML→document→window,通常会将目标阶段看做冒泡阶段的一部分。对象
事件的传播路径在事件开始传播以前就已经肯定,若是在传播过程当中DOM结构发生变化,那么也会按照根据原来的结构而生成的传播路径进行传播。blog
添加事件监听:addEventListener(eventType, function, useCapture)教程
useCapture为true,表示该监听器监听捕获阶段的事件,useCapture为false,则监听冒泡阶段的事件。若是是在事件目标元素上添加的事件监听,那么无论useCapture设置为true仍是false都能监听到,并且此时的事件阶段eventPhase为2。事件
简单来讲就是为某个元素添加一个事件监听器,当有事件传播到该元素时,若是事件处于捕获阶段,eventPhase=1,那么设置了useCapture为true的监听器就会响应。若是事件传播到该元素时处于冒泡阶段,eventPhase=3,那么设置了useCapture为false的监听器响应。若是事件传播到该元素时处于目标阶段,eventPhase=2,那么无论是设置了true仍是false的监听器都能响应。ip
下面是个例子:文档
<div class="first">first <div class="second"> <label>click me <input type="checkbox" class="target"> </label> </div> </div> <script>
let div1=document.getElementsByClassName('first')[0], div2=document.getElementsByClassName('second')[0], target=document.getElementsByClassName('target')[0];
div1.addEventListener('click',(e)=>{ alert('first: '+e.eventPhase+'1'); },true); div1.addEventListener('click',(e)=>{ alert('first: '+e.eventPhase+'2'); },true); div1.addEventListener('click',(e)=>{ alert('first: '+e.eventPhase); },false); div2.addEventListener('click',(e)=>{ alert('second: '+e.eventPhase); },true); div2.addEventListener('click',(e)=>{ alert('second: '+e.eventPhase); },false); target.addEventListener('click',(e)=>{ alert('target: '+e.eventPhase); },true); target.addEventListener('click',(e)=>{ alert('target: '+e.eventPhase); },false); </script>
点击CheckBox后,依次弹出“first:11”、“first:12”、“second:1”、“target:2”、“target:2”、“second:3”、“first:3”。get
那么stopPropagation()方法就比较好理解了,若是在某个阶段在事件监听函数里调用了事件的stopPropagation()方法,那么该事件后续的传播过程都会被阻止,不管是捕获仍是处于目标或者冒泡。
好比你在捕获阶段监听到事件,而且调用了stopPropagation()方法,那么后续的在子节点上的捕获过程(若是还有的话,若是是在捕获过程的最后一个节点上调用,那么就是直接阻止将要进行的目标过程)、处于目标过程、冒泡过程都会被阻止。若是在处于目标阶段调用,那么后续的冒泡过程被阻止。若是在冒泡过程调用,后续的在其余节点上的冒泡传播被阻止。
这里要注意的是,stopPropagation()是以元素节点间的传播为单位,若是后续的传播过程当中事件须要从一个元素节点流转到它的父节点或子节点,那么stopPropagation()将会阻止这样的过程。也就是说在一个元素节点内,它是不起做用的,若是在调用stopPropagation()的元素节点内还存在其余事件监听,而且与调用stopPropagation()的监听器的响应规则是同样的,那么这些其余的监听依然会按顺序响应。
例如在上面的例子中,first有两个捕获阶段的事件监听器,在第一个里调用stopPropagation(),那么第二个监听依然会响应,可是后续的捕获、目标和冒泡过程都被阻止了。所以最终只会弹出first的两个弹窗:“first:11”、“first:12”;
而stopImmediatePropagation()在调用后就直接阻止事件的后续传播过程了,包括同一个元素节点内的过程。若是在调用stopImmediatePropagation()的元素节点上还有其余事件监听,那么在调用stopImmediatePropagation()后,其余事件监听也不会响应。
仍是以上面的例子,若是在first的第一个捕获监听器里调用stopImmediatePropagation(),那么最终只会有一个弹窗:“first:11”。
若是在target的捕获监听器里调用stopImmediatePropagation(),弹窗结果是:“first:11”、“first:12”、“second:1”、“target:2”。
不少人把preventDefault()和阻止冒泡、捕获搞混,实际上preventDefault()和事件传播过程没有关系,它的做用是阻止事件的默认行为,例如a标签在click事件后的默认行为是连接到新的页面,checkbox的click事件默认行为是将修改的选中状态生效。preventDefault()的做用则是阻止这些默认行为,它不会影响事件的传播,并且不管在事件的哪一个阶段调用preventDefault(),都能阻止默认行为。