写在前面的话
事件委托是利用事件冒泡机制,在只指定一个事件处理程序的前提下就可以管理一类型的所有事件。网络上关于事件委托说的最后的一个例子就是取快递,是不是蜂巢也可以用来恰当的举例呢。
这个小区有3个住户,为了签收快递,有两种办法:一是每天就在家里等着快递员送货上门;二就是快递来了之后蜂巢代为签收。这样即使小区里又新加了住户的话,签收快递这件事情也一样可以交给蜂巢统一办理。
这里强调了两点:
1、现在委托蜂巢的这些住户的快递都是有快递需要代为签收的,即程序中的所有dom节点都是有事件的;
2、新住进来的住户的快递也是可以委托蜂巢的,也就是说,程序中新加的dom节点也是有事件的。
事件冒泡
当HTML中出现DOM结构的嵌套,并且子元素和父元素都绑定了相同的事件,这里以click事件为例。当最里面的子元素的点击事件被触发时,先触发子元素的时间处理器,再触发父元素的事件处理器,一直向上触发相同事件,直到document为止。这就像是鱼吐泡泡,从底下一直向上层跑,每经过一层就要检查是否有事件处理器,有的话就触发,没有就一直向上寻找。
事件捕获
事件捕获则和时间冒泡相反,点击的时候,从最外层向里逐层触发,直到点击位置的最底层,也就是通常说的事件的target。
在日常搬砖过程中,我们可以依据这两种方式的不同,实现产品的各种天花乱坠的需求。
在上篇中讲JS事件绑定的文章中,讲到了DOM2级事件绑定中,有第三个参数,这是一个布尔类型的值,决定的是这个事件的事件流处理方式。默认为false,表示事件流处理器是在冒泡阶段触发执行,当为true时,表示在捕获阶段执行。
<template>
<div id="dom1">
<div id="dom2">
<div id="dom3"></div>
</div>
</div>
</template>
<script>
export default {
mounted() {
let dom1 = document.getElementById("dom1"),
dom2 = document.getElementById("dom2"),
dom3 = document.getElementById("dom3");
//绑定事件,冒泡阶段执行
dom1.addEventListener("click", this.func1, false);
dom2.addEventListener("click", this.func2, false);
dom3.addEventListener("click", this.func3, false);
},
methods: {
func1() {
console.log("点击dom1");
},
func2() {
console.log("点击dom2");
},
func3() {
console.log("点击dom3");
}
}
};
冒泡阶段执行,即从里向外执行。在这种情况下,点击区域内的任何地方,控制台输出的结果均为
//绑定事件,捕获阶段执行
dom1.addEventListener("click", this.func1, true);
dom2.addEventListener("click", this.func2, true);
dom3.addEventListener("click", this.func3, true);
用法
假设一种情况,在一个ul-li的DOM结构中,需要点击li时执行某个操作。这个时候,可以遍历所有li并绑定相关事件。但是,如果有100个,1000个li呢,难道要遍历1000次,进行1000次的事件绑定和事件移除吗?
这个时候就可以利用事件冒泡在外层ul上绑定点击事件,这样,在点击li时,外层ul上的点击事件将在冒泡阶段执行。轻松便捷,是我要的高质量代码。
阻止事件传播
面对千变化万,五花八门的需求,有些时候,我们不需要事件冒泡或者事件捕获。那么怎么去阻止事件传播呢?
想要阻止事件传播,首先要弄明白事件的传播机制。
事件传播机制
DOM2级事件规定,事件流包括三个阶段,事件捕获阶段(capturing-phase)、处于目标阶段(at-target)、事件冒泡阶段(bubbling-phase)。
当一个事件触发后,它会在不同节点之间传播(propagation)。当点击inner,触发了inner的click事件。浏览器在执行inner的click事件之前,
-
捕获阶段:从整个页面document开始向内查找,把inner的祖先全部遍历一遍(为冒泡阶段的传播路径做准备)
-
目标阶段:找到事件源,将事件源上绑定的方法执行
-
冒泡阶段:从目标节点传导回document。
这种三阶段的传播模型,会使得一个事件在多个节点上触发。
阻止事件传播
event.stopPropagation()
stopPropagation方法阻止事件在DOM中继续传播,即取消进一步的事件捕获或冒泡,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上新定义的事件监听函数。<template> <div id="dom1"> <div id="dom2"> <div id="dom3"></div> </div> </div> </template> <script> export default { mounted() { let dom1 = document.getElementById("dom1"), dom2 = document.getElementById("dom2"), dom3 = document.getElementById("dom3"); dom1.addEventListener("click", this.func1, false); dom2.addEventListener("click", this.func2, false); dom3.addEventListener("click", this.func3, false); }, methods: { func1(e) { console.log("点击dom1"); }, func2() { console.log("点击dom2"); }, func3(e) { console.log("点击dom3"); //阻止事件传播 e.stopPropagation(); } } }; </script>此时点击最里层dom,控制台的打印结果位:
写在后面
stopPropagation方法是个web API。当我们在平时开发过程中,框架给我们提供了便捷的事件绑定方法。例如,VUE中的v-bind:click指令。如果需要阻止事件传播,只需要v-bind:click.stop指令即可。