事件冒泡、事件捕获、默认事件与事件代理(委托)

17年的关于事件冒泡和事件捕获的总结还是相对片面,现在更新一下。 – 2019.07.16

事件冒泡和事件捕获分别由微软网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。W3C采用折中的方式,平息了战火,制定了统一的标准–先捕获再冒泡

DOM0 & DOM2

在DOM的发展中,有规定DOM0、DOM2级绑定事件。

DOM0

DOM0事件就是通过onclick直接绑定到html事件上的,不存在兼容性问题,很早期。

DOM2

DOM2事件是通过element.addEventListener(event, function, useCapture)函数来绑定事件的。

DOM2规定了事件传播分为三个阶段:

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段

mark

addEventListener第三个参数为useCapture,值为true/fasle。设定事件是在捕获阶段触发还是在冒泡阶段触发。

  • fasle,默认,冒泡阶段触发
  • true,捕获阶段触发

或者直接在事件执行函数functionreturn false or true同样可以控制冒泡或捕获。

function的event参数

函数的隐藏参数event包含了很多属性和方法,且一些浏览器不太相同,但是都包含一些常见的。

  • bubble : 表明事件是否冒泡
  • cancelable : 表明是否可以取消冒泡
  • currentTarget : 当前时间程序正在处理的元素, 和this一样的;
  • defaultPrevented: false ,如果调用了preventDefualt这个就为真了;
  • detail: 与事件有关的信息(滚动事件等等)
  • eventPhase: 如果值为1表示处于捕获阶段, 值为2表示处于目标阶段,值为3表示在冒泡阶段
  • target || srcElement: 事件的目标
  • trusted: 为ture是浏览器生成的,为false是开发人员创建的(DOM3)
  • type : 事件的类型
  • preventDefault() 阻止默认事件;
  • stopPropagation() 取消冒泡或者捕获;
  • stopImmediatePropagation() (DOM3)阻止任何事件的运行;

事件冒泡

点击child,先进行事件捕获阶段,而后在事件冒泡阶段触发child事件,再冒泡到parent,触发父级事件(会一直冒泡到文档根);
这个过程是可以阻止的,方法event.stopPropagation():(多益二笔考了这个,结果我给忘了……)

事件捕获

点击child,先进行事件捕获阶段,触发parent事件,再逐级往下进行捕获到child,触发child事件;再进行冒泡,冒泡阶段不执行。

两个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="parent">
parent
<div id="child">
child
</div>
</div>
<script>
document.getElementById("parent").addEventListener("click", function() {
console.log("parent");
}, false); // 冒泡

document.getElementById("child").addEventListener("click", function() {
console.log("child");
}, true); // 捕获
</script>

点击parent,输出parent
点击child,输出child parent;由于父级事件定义为冒泡阶段执行,所以先输出child,后输出parent;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="parent">
parent
<div id="child">
child
</div>
</div>
<script>
document.getElementById("parent").addEventListener("click", function() {
console.log("parent");
}, true); // 捕获

document.getElementById("child").addEventListener("click", function() {
console.log("child");
}, true); // 捕获
</script>

点击parent,输出parent
点击child,输出parent child;由于父级事件定义为捕获阶段执行,所以先输出parent,后输出child;

默认事件

浏览器具有默认行为,诸如a标签的点击跳转,右键点击跳出菜单等;
阻止默认事件:event.preventDefault();(多益二笔和阻止默认事件一起考的,这个我记得);

事件冒泡和事件捕获的选择

其中addEventListener()中 true为捕获,false为冒泡,默认false

1
2
3
4
obj.addEventListener("click",function(){
},true) //捕获
obj.addEventListener("click",function(){
},false) //冒泡

事件代理(委托)

以下参考了js中的事件委托或是事件代理详解,并做了精简和提炼

首先,事件代理是利用了事件冒泡的!(之前一直好奇事件冒泡这种不方便的东西有什么用…..直到了解了事件代理)
比如现在的需求是有100个li标签,每个都要给他设置个点击事件;最直接的方法是写个for循环,遍历添加事件。

但是在JavaScript中,添加的事件处理程序数量将关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;

给每个li添加事件

1
2
3
4
5
6
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
1
2
3
4
var oUl = document.getElementById("ul");
oUl.onclick = function(){
alert(123);
}

当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发。
但是,这里当点击ul的时候,也是会触发的,那么如果我想让事件代理的效果跟直接给节点的事件效果一样怎么办?比如说只有点击li才会触发。

Event对象提供了一个属性叫target,可以返回事件的目标节点,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,而且,这个是有兼容性的,标准浏览器用ev.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的。

1
2
3
4
5
6
7
8
9
10
var oUl = document.getElementById("ul");
oUl.onclick = function(e){
 //兼容IE
 var e = e || window.event;
 var target = e.target || e.srcElement;
 if(target.nodeName == 'LI'){
alert(123);
alert(target.innerHTML);
}
}

给每个子节点添加不同事件

之前做了个购物车的DEMO
在一个tr中,几个td的点击事件不同,这是就要用到switch

1
2
3
4
5
6
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var oBox = document.getElementById("box");
oBox.onclick = function (e) {
var e = e || window.event;
var target = e.target || e.srcElement;
if(target.nodeName == 'INPUT'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}

增加子节点时

当父级不使用代理事件,给其添加子节点时,子节点的点击事件也要添加!这就又增加了一个dom操作。
当父级代理事件时,该子节点不用添加点击事件就会因为事件冒泡触发父级的代理事件!