解析JavaScript的event事件传递顺序和类型,如何阻止冒泡?
发布于 作者:苏南大叔 来源:程序如此灵动~本文描述JavaScript
的事件传递顺序,有capture
和bubble
两种类型,对应的说的就是事件传递顺序。当多个嵌套元素都监控同一个事件的时候,就会发生事件传递顺序的问题。默认情况下,事件传递是bubble
类型的,从里到外进行冒泡事件传递。通过更改addEventListener
的useCapture
参数,可以修改事件传递顺序。事件的新顺序,是在一定范围内反向,而不是全部反向。
苏南大叔的程序如此灵动博客,记录苏南大叔和计算机代码的故事。测试环境:谷歌浏览器。
e.eventPhase
关于冒泡的顺序问题,这里有个专用的变量.eventPhase
用于进行表述区分。它的取值有以下几种:
.eventPhase | 官方说明 | 含义 |
---|---|---|
0 | none | |
1 | capturing | 从外到内反向 |
2 | target | 事件的直接作用对象 |
3 | bubbling | 默认从内到外冒泡 |
其它 | error |
var doClick = function (e) {
var level = "";
switch (e.eventPhase) {
case 0:
level = "none";
break;
case 1:
level = "capturing";
break;
case 2:
level = "target";
break;
case 3:
level = "bubbling";
break;
default:
level = "error";
}
console.log(e.type, e.target.id, e.currentTarget.id, e.eventPhase, level);
// click c1 c1 2 target
}
区分target
和currentTarget
,可以参考:
useCapture
使用参数useCapture
来控制是否在一定程度上反向,取值如下:
useCapture | 含义 |
---|---|
true | 在目标上反向 |
false | 默认值冒泡 |
值得特别说明的是:只有三个参数都完全一致的时候,removeEventListener
才能remove
掉对应的事件。
addEventListener(event, function, useCapture)
removeEventListener(event, function, useCapture)
对于第三个参数位置useCapture
,还可以表述为option
对象,由于使用到的概率太小,所以这里不做描述。更多参数可以参考这里:
测试代码
<style>
div {
border: 1px solid gray;
margin: 10px;
padding: 10px;
box-sizing: border-box;
}
#c0, #c2, #c4 {
background-color: rgb(240, 201, 31);
}
#c1, #c3 {
background-color: rgb(226, 223, 223);
}
</style>
<div id="c0">
<div id="c1">
<div id="c2">
<div id="c3">
<div id="c4">
c4
</div>
</div>
</div>
</div>
</div>
事件监听:
var doClick = function (e) {
var level = "";
switch (e.eventPhase) {
case 0:
level = "none";
break;
case 1:
level = "capturing";
break;
case 2:
level = "target";
break;
case 3:
level = "bubbling";
break;
default:
level = "error";
}
console.log(e.type, e.target.id, e.currentTarget.id, e.eventPhase, level);
}
var c0 = document.getElementById("c0");
var c1 = document.getElementById("c1");
var c2 = document.getElementById("c2");
var c3 = document.getElementById("c3");
var c4 = document.getElementById("c4");
c0.addEventListener("click", doClick);
c1.addEventListener("click", doClick);
c2.addEventListener("click", doClick);
c3.addEventListener("click", doClick);
c4.addEventListener("click", doClick);
变化的就是最后几行代码,增加,true
字样。例如:
c0.addEventListener("click", doClick, true);
测试结果
那么看一下这个的五层div
的传递情况是怎么样的呢?
默认情况下,冒泡向外传递事件,并不会向内传递(c0
是最外层,c4
是最内层)。所以,
- 点击
c0
的时候,只有c0
会收到事件,c1/c2/c3/c4
不会接收到事件。 - 点击
c1
的时候,只有c0/c1
会收到事件,c2/c3/c4
不会接收到事件。 - 点击
c2
的时候,只有c0/c1/c2
会收到事件,c3/c4
不会接收到事件。 - 点击
c3
的时候,只有c0/c1/c2/c3
会收到事件,c4
不会接收到事件。
c0 | c1 | c2 | c3 | c4 | 点击 | 事件顺序 |
---|---|---|---|---|---|---|
false | false | false | false | false | c0 | c0 |
false | false | false | false | false | c1 | c1 => c0 |
false | false | false | false | false | c2 | c2 => c1 => c0 |
false | false | false | false | false | c3 | c3 => c2 => c1 => c0 |
false | false | false | false | false | c4 | c4 => c3 => c2 => c1 => c0 |
true | false | false | false | false | c0 | c0 |
true | false | false | false | false | c1 | c0 => c1 |
true | false | false | false | false | c2 | c0 => c2 => c1 |
true | false | false | false | false | c3 | c0 => c3 => c2 => c1 |
true | false | false | false | false | c4 | c0 => c4 => c3 => c2 => c1 |
false | true | false | false | false | c0 | c0 |
false | true | false | false | false | c1 | c1 => c0 |
false | true | false | false | false | c2 | c1 => c2 => c0 |
false | true | false | false | false | c3 | c1 => c3 => c2 => c0 |
false | true | false | false | false | c4 | c1 => c4 => c3 => c2 => c0 |
false | false | true | false | false | c0 | c0 |
false | false | true | false | false | c1 | c1 => c0 |
false | false | true | false | false | c2 | c2 => c1 => c0 |
false | false | true | false | false | c3 | c2 => c3 => c1 => c0 |
false | false | true | false | false | c4 | c2 => c4 => c3 => c1 => c0 |
false | false | false | true | false | c0 | c0 |
false | false | false | true | false | c1 | c1 => c0 |
false | false | false | true | false | c2 | c2 => c1 => c0 |
false | false | false | true | false | c3 | c3 => c2 => c1 => c0 |
false | false | false | true | false | c4 | c3 => c4 => c2 => c1 => c0 |
false | false | false | false | true | c0 | c0 |
false | false | false | false | true | c1 | c1 => c0 |
false | false | false | false | true | c2 | c2 => c1 => c0 |
false | false | false | false | true | c3 | c3 => c2 => c1 => c0 |
false | false | false | false | true | c4 | c4 => c3 => c2 => c1 => c0 |
false | true | false | true | false | c0 | c0 |
false | true | false | true | false | c1 | c1 => c0 |
false | true | false | true | false | c2 | c1 => c2 => c0 |
false | true | false | true | false | c3 | c1 => c3 => c2 => c0 |
false | true | false | true | false | c4 | c1 => c3 => c4 => c2 => c0 |
结论是:
- 设置
useCapture
为true
,只会影响其内部的元素被点击时的排序,不影响其自身及外部元素被点击时的排序。 - 对于其内部的元素的点击,设置
useCapture
为true
的优先级高,越外层的优先级越高。
设置双向传递
对于同一个元素,可以同时设置向内和向外。就是说下面的函数定义是正常可以运行的。
e1.addEventListener("click", doClick, true);
e1.addEventListener("click", doClick, false);
c0 | c1 | c2 | c3 | c4 | 点击 | 事件顺序 |
---|---|---|---|---|---|---|
双向 | false | false | false | false | c0 | c0 => c0 |
双向 | false | false | false | false | c1 | c0(逆) => c1 => c0(泡) |
双向 | false | false | false | false | c2 | c0(逆) => c2 => c1 => c0(泡) |
双向 | false | false | false | false | c3 | c0(逆) => c3 => c2 => c1 => c0(泡) |
双向 | false | false | false | false | c4 | c0(逆) => c4 => c3 => c2 => c1 => c0(泡) |
false | 双向 | false | false | false | c0 | c0 |
false | 双向 | false | false | false | c1 | c1 => c1 => c0 |
false | 双向 | false | false | false | c2 | c1(逆) => c2 => c1(泡) => c0 |
false | 双向 | false | false | false | c3 | c1(逆) => c3 => c2 => c1(泡) => c0 |
false | 双向 | false | false | false | c4 | c1(逆) => c4 => c3 => c2 => c1(泡) => c0、 |
可见,如果对同一个元素设置双向事件,那么,就会把逆向的事件顺序提前(或者说凭空出现一个高优先级),而原有的冒泡事件顺序不变。
阻止事件传递
阻止冒泡,阻止事件传递:
e.stopPropagation();
阻止默认行为:
e.preventDefault();
比如还是上面的五层div
的例子,设置当c2
的时候,阻止事件传递。
if(e.currentTarget.id=="c2"){
e.stopPropagation();
}
那么,上述表格里面,所有的c2
后面的传递顺序都不存在了(但是c2
被传递到了)。
在这里苏南大叔发散一下思维:原本在冒泡顺序之外的元素,按理说可以通过.stopPropagation()
把事件截至住。但是!如果外部的元素设置了useCapture
,那么就可以强势插入事件顺序,甚至可以截获本来属于别人的事件。
比如click
事件,如果不是写的a
标签,而是注册click
事件的话,那么,就存在着可能性来截获内部的点击事件了。
结束语
更多js
相关经验文字,请点击:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。