目录:
一、事件冒泡
微软提出了名为事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
<html>
<body>
<div id="outer">
<p id="inner">Click me!</p>
</div>
</body>
</html>
在事件冒泡的概念下在p元素上发生click事件的顺序应该是p -> div -> body -> html -> document。
即如果p元素上绑定了click的处理事件将先被触发否则将冒泡到div,在div上如果绑定了click的处理事件将被触发否则将冒泡到body,以此类推,判断是否有click事件的处理函数,有就触发,没有就冒泡到上一级直到到达document级。
示例:
<div id="s1">s1
<div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){
console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){
console.log("s2 冒泡事件");
},false);
</script>
//点击s2:
//s2 冒泡事件
//s1 冒泡事件
//点击s1:
//s1 冒泡事件
二、事件捕获
网景提出另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
<html>
<body>
<div id="outer">
<p id="inner">Click me!</p>
</div>
</body>
</html>
在事件捕获的概念下在p元素上发生click事件的顺序应该是document -> html -> body -> div -> p
即如果document元素上绑定了click的处理事件将先被触发否则将进入到html中,在html上如果绑定了click的处理事件将被触发否则将进入到body中,以此类推,判断是否有click事件的处理函数,有就触发,没有就进入到下一级直到到达到触发元素那一级。
示例:
<div id="s1">s1
<div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){
console.log("s1 捕获事件");
},true);
s2.addEventListener("click",function(e){
console.log("s2 捕获事件");
},true);
</script>
//点击s2:
//s1 捕获事件
//s2 捕获事件
//点击s1:
//s1 捕获事件
三、addEventListener的第三个参数
在上面的案例中,我们对addEventListener的第三个参数进行了设置,下面将介绍下addEventListener函数第三个参数的含义。addEventListener的第三个参数就是为冒泡和捕获准备的.
addEventListener有三个参数:
element.addEventListener(event, function, useCapture)
- 第一个参数是需要绑定的事件
- 第二个参数是触发事件后要执行的函数
- 第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。
四、事件冒泡与事件捕获同时存在
当事件捕获和事件冒泡一起存在的情况,事件又是如何触发呢。
这里记被点击的DOM节点为target节点:
1、document 往 target节点,捕获前进,遇到注册的捕获事件立即触发执行
2、到达target节点,触发事件(对于target节点上,是先捕获还是先冒泡则捕获事件和冒泡事件的注册顺序,先注册先执行)
3、target节点 往 document 方向,冒泡前进,遇到注册的冒泡事件立即触发
示例:
//对于非target节点则先执行捕获在执行冒泡
//对于target节点则是先执行先注册的事件,无论冒泡还是捕获
<div id="s1">s1
<div id="s2">s2</div>
</div>
<script>
s1.addEventListener("click",function(e){
console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){
console.log("s2 冒泡事件");
},false);
s1.addEventListener("click",function(e){
console.log("s1 捕获事件");
},true);
s2.addEventListener("click",function(e){
console.log("s2 捕获事件");
},true);
</script>
点击s2,执行结果如下:
s1 捕获事件
s2 冒泡事件
s2 捕获事件
s1 冒泡事件
分析:
点击s2,click事件从document->html->body->s1->s2(捕获前进)
这里在s1上发现了捕获注册事件,则输出"s1 捕获事件"
到达s2,已经到达目的节点,
s2上注册了冒泡和捕获事件,先注册的冒泡后注册的捕获,则先执行冒泡,输出"s2 冒泡事件"
再在s2上执行后注册的事件,即捕获事件,输出"s2 捕获事件"
下面进入冒泡阶段,按照s2->s1->body->html->documen(冒泡前进)
在s1上发现了冒泡事件,则输出"s1 冒泡事件"
五、如何阻止事件冒泡或者事件捕获
以冒泡事件为例:
1、通过event.stopPropagation()终止事件传播
对某一个节点而言,如果不想它现在处理的事件继续往上冒泡的话,我们可以终止冒泡,即在相应的处理函数内,加入event.stopPropagation(),终止事件的广播分发,这样事件停留在本节点,不会再往外传播了。
<div id="s1">s1
<div id="s2">s2</div>
</div>
s1.addEventListener("click",function(e){
console.log("s1 冒泡事件");
},false);
s2.addEventListener("click",function(e){
e.stopPropagation(); //阻止冒泡到上面元素,冒泡链在此处中断
console.log("s2 冒泡事件");
},false);
//原先点击s2:
//s2 冒泡事件
//s1 冒泡事件
//阻止冒泡后点击s2:
//s2 冒泡事件
2、对触发事件元素进行过滤
事件包含最初触发事件的节点引用 和 绑定处理事件节点的引用,那如果节点只处理自己触发的事件即可,不是自己产生的事件不处理。
event.target指向的是触发事件的dom节点,而event.currrentTarget 指向的是绑定事件的dom节点,我们可以通过这两个target 是否相等,让绑定事件的dom节点只处理其自身触发的事件而不是下级冒泡传递上来的。
s1.addEventListener("click",function(event){
if(event.target === event.currentTarget){
console.log("s1 冒泡事件");
}
},false);
s2.addEventListener("click",function(event){
if(event.target === event.currentTarget){
console.log("s2 冒泡事件");
}
},false);
我们还可以获取target的节点名、id等属性进行过滤设置处理事件。
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
var color_list = document.getElementById('color-list');
color_list.addEventListener('click',showColor,false);
function showColor(e){
var x = e.target;
if(x.nodeName.toLowerCase() === 'li'){
console.log('The color is ' + x.innerHTML);
}
}
元素收到事件后,判断事件是否符合要求,然后做相应的处理,然后事件继续冒泡往上传递;
通过以上方式,我们把本来每个元素都要有的处理函数,都交给了其祖父节点元素来完成了,也就是说,li将自己的响应逻辑委托给ul,让它来完成相应逻辑,自己不实现相应逻辑,这个模式,就是所谓的事件委托。