一、事件冒泡和捕获
运行条件:当一个事件发生在具有父元素的的元素上时,现代浏览器根据事件添加时的设置来执行(冒泡或者捕获)
通过 addEventListener() 的第三个属性来设置事件是通过捕获阶段注册的(true),还是冒泡阶段注册的(false)。默认情况下是false。
1、事件冒泡
从实际操作的元素(事件)向上级父元素一级一级执行下去,直到达到
<html>
有些时候父元素和子元素都定义了click事件,但是不希望点击子元素的时候执行父元素的click事件(例如dialog弹窗的遮罩层如果是父元素,而dialog弹窗内容层是子元素,同时可以通过点击遮罩层来关闭弹窗,但是点击内容层不关闭弹窗),可以通过stopPropagation()在子元素上阻止冒泡。
2、事件捕获(一般不会用到)
浏览器检查元素的最外层祖先
<html>
,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
然后,它移动到<html>
中的下一个元素(点击的元素的父元素),并执行相同的操作,然后是下一个元素(点击的元素的父元素),依此类推,直到到达实际点击的元素。
二、事件捕获和冒泡的区别(执行顺序的不同)
// css
#div1 {
width: 400px;
height: 400px;
background: #f00;
}
#div2 {
width: 300px;
height: 300px;
background: #0f0;
}
#div3 {
width: 200px;
height: 200px;
background: #00f;
}
#div4 {
width: 100px;
height: 100px;
background: #f0f;
}
// html
<div id="div1">我是div1
<div id="div2">我是div2
<div id="div3">我是div3
<div id="div4">我是div4</div>
</div>
</div>
</div>
</body>
// javascript
var div1=document.getElementById("div1");
var div2=document.getElementById("div2");
var div3=document.getElementById("div3");
var div4=document.getElementById("div4");
div1.addEventListener("click",function(){
alert("我是div1");
})
div2.addEventListener("click",function(){
alert("我是div2");
})
div3.addEventListener("click",function(){
alert("我是div3");
})
div4.addEventListener("click",function(){
alert("我是div4");
})
冒泡的执行顺序:当我们点击div1的时候,只弹出“我是div1”,但是当我们点击div2的时候,就先弹出“我是div2”,再弹出“我是div1”,当我们点击div4 的时候,则是4-3-2-1这样的顺序,这样就叫做冒泡,它就像鱼儿吐泡泡一样,从下到上,泡泡从水下上来泡泡会变得越来越大,从这个角度思考,先从子元素开始执行,然后是父元素,再然后是祖先元素,他们的等级也是越来越大的。在addEventListener里,false是默认的,表示的是冒泡。
捕获的执行顺序:在addEventListener里,第三个参数设置为true,表示的是捕获。将上面的代码中添加进第三个参数true,再运行代码,会发现这时,当我们点击div4的时候,出现的顺序是1-2-3-4,它是从祖先元素开始慢慢找,最后找到我们的点击目标,可以将这个行为理解为就像警察叔叔抓坏人一样,逐渐的缩小抓捕范围,最后确定到某一个人身上,所以这个过程叫做捕获。
dom元素中,既有冒泡,又有捕获的执行顺序:w3c规定,任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段。示例:
div1.addEventListener("click",function(){
alert("div1");
}, false);
div2.addEventListener("click",function(){
alert("div2");
}, true);
div3.addEventListener("click",function(){
alert("div3");
}, false);
div4.addEventListener("click",function(){
alert("div4");
}, true);
这时的div1和div3是冒泡事件,div2和div4是捕获事件,当我们点击div4以后,弹出的顺序是2-4-3-1。因为我们先执行捕获过程,在这个例子中div2和div4是捕获的,那么捕获又是从大到小,所以,先弹出div2,再弹出div4,捕获结束以后就该是冒泡了。冒泡的顺序是从小到大,从子到父,所以就先弹出div3,再就是div1,所以最后的顺序是2-4-3-1。如果我们点击div3,结果会是2-3-1,同样的道理,先捕获,捕获是从div1开始到div3,这中间只有div2是捕获,div4并没有执行到,因为我们点击的目标是div3,后面的步骤和前面的过程一样,先3后1。
最后一个问题,代码示例:
div1.addEventListener("click",function(){
alert("div1");
}, false);
div2.addEventListener("click",function(){
alert("div2_捕获");
}, true);
div2.addEventListener("click",function(){
alert("div2_冒泡");
}, false);
那么,当我们点击div2的时候,结果是 div2_捕获 -> div_2冒泡 -> div1的顺序,代码修改:
div1.addEventListener("click",function(){
alert("div1");
}, false);
div2.addEventListener("click",function(){
alert("div2_冒泡");
}, false);
div2.addEventListener("click",function(){
alert("div2_捕获");
}, true);
这时当我们点击div2的时候,结果是 div2_冒泡 -> div_2捕获 -> div1的顺序
综上结论:绑定在被点击元素的事件是按照代码的顺序发生的,其他非绑定的元素则是通过冒泡或者捕获的触发。按照W3C的标准,先发生捕获事件,后发生冒泡事件。
事件的整体顺序是:非目标元素捕获 -> 目标元素代码顺序 -> 非目标元素冒泡。
三、事件委托
1、使用场景
如果你想要在大量子元素(包括动态添加的)中单击任何一个就可以运行一段代码,这个时候可以把事件监听器设置在父节点上。
2、好处
- 只在内存中开辟了一块空间,节省资源同时减少了dom操作,提高性能
- 对于新添加的元素也会有之前的事件
3、事件委托同捕获和冒泡的关系
- 事件捕获和冒泡是现代浏览器的执行事件的两个不同阶段
- 事件委托是利用冒泡阶段的运行机制来实现的
4、实例:ul中触发每个li来改变他们的背景颜色
// html
<ul id='ul'>
<li>111111</li>
<li>222222</li>
<li>333333</li>
</ul>
<button id='button'>添加元素</button>
// javascript
window.onload = function() {
let oUl = document.getElementById('ul');
let aLi = oUl.getElementsByTagName('li');
let but = document.getElementById('button');
let now = 3;
// 事件源:event对象,不管在哪个事件中,只要你操作的哪个元素就是事件源
// ie:window.event.srcElement
// 标准:event.target
oUl.onmouseover = function(e){
let ev = e || window.event;
let target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = 'red';
}
}
oUl.onmouseout = function(e){
let ev = e || window.event;
let target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){
target.style.background = '';
}
}
but.onclick = function(){
now ++;
let newLi = document.createElement('li');
newLi.innerHTML = 111111 * now;
oUl.appendChild(newLi);
}
}