先来看一个例子,看下面这个代码
<div id="div1" onclick="test()">
<div id="div2" onclick="test()">
<div id="div3" onclick="test()">
</div>
</div>
</div>
三个嵌套的<div>
每一个都绑定了一个点击事件
那么当我们点击最内层的<div>
时,是不是也相当于点击了中层和外层的<div>
呢?
似乎从理论是来说是这样的,如果真的是这样那外层的点击事件会不会被触发呢?
让我们来试一下
function test(){
console.log("test");
}
经测试发现点击最内层的<div>
时事件被触发了三次
不如我们干脆更细致一点
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#div1 {
width: 200px;
height: 200px;
border: 1px solid red;
}
#div2 {
width: 100px;
height: 100px;
border: 1px solid green;
}
#div3 {
width: 50px;
height: 50px;
border: 1px solid blue;
}
</style>
</head>
<body>
<div id="div1" onclick="test1()">
<div id="div2" onclick="test2()">
<div id="div3" onclick="test3()">
</div>
</div>
</div>
<script>
function test1() {
console.log("1");
}
function test2() {
console.log("2");
}
function test3() {
console.log("3");
}
</script>
</body>
</html>
这段代码里我们给不同的<div>
设置了不同的函数,打印出不同的数字
再次尝试点击最内层之后我们发现控制台按顺序打出了’3’ ‘2’ ‘1’
这说明从我们所点击的最内层开始,向外扩散,每一层的点击事件都被触发了,而且是由内向外的顺序。
而我们点击中间一层,打出了’2’ ‘1’
这似乎也很好理解,毕竟从视觉上我们也能看出来应该是这样。
那么我们换一种方式
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#div1 {
width: 200px;
height: 200px;
border: 1px solid red;
}
#div2 {
width: 100px;
height: 100px;
border: 1px solid green;
}
#div3 {
width: 50px;
height: 50px;
border: 1px solid blue;
}
</style>
</head>
<body>
<div id="div1" >
<div id="div2" >
<div id="div3" >
</div>
</div>
</div>
<script>
function test1() {
console.log("1");
}
function test2() {
console.log("2");
}
function test3() {
console.log("3");
}
var oDiv=document.getElementsByTagName("div");
oDiv[0].addEventListener("click",test1,true);
oDiv[1].addEventListener("click",test2,true);
oDiv[2].addEventListener("click",test3,true);
</script>
</body>
</html>
先来解释一下做出的变动,这此我们采用的addEventListener的方式来绑定事件,第一个参数是事件名称,比如’click’,‘mouseover’,第二个参数是函数名,即所要绑定的函数,而第三个参数就比较特殊,我们稍后再说。
现在让我们点击最内层的<div>
,不可思议的事情发生了
这次打印出的是 ‘1’ ‘2’ ‘3’;
按照之前的经验应该由内向外,但这次却是由外向内!
没错,这就牵扯到了我们所要说的事件流!
实际上无论我们是否绑定事件,每一个动作都会在对应的元素上触发,即每一个元素实际上都无时无刻不在触发着事件。只不过这些事件因为没有绑定函数,所以不会产生效果罢了。
以点击为例,当我们点击一个元素的时候,这个元素就会产生一个点击事件,这个点击事件并不是直接产生在这个元素上并直接作用的,而要“流”。
事件流主要分为两方面,一种是事件捕获,一种是事件冒泡。
事件冒泡:顾名思义,就是像冒泡泡一样不断的向上冒,在这里就是从产生事件的元素一层层的向父级元素扩散,直到扩散到html或者window对象。
事件捕获:刚好相反,当某个元素产生一个事件时,并不是直接在这个元素上产生,而是从最外层的window或者html开始产生一个事件,一层一层的向内层传递,直到该元素为止。
这两种不同的方式的产生和发展这里就不作讨论了。
当前的主流浏览器都是同时支持事件捕获和事件冒泡的。
我们说当前主流浏览器两者都支持,他们将这两者结合到一起,当产生一个事件时,先发生事件捕获,即事件捕获阶段,事件捕获到产生事件的具体元素上,开始判断有无事件句柄(绑定函数),即处于目标阶段,然后事件由该元素开始进行事件冒泡,即事件冒泡阶段。这三个阶段整合在一起就是事件流。
(实际上事件在每个节点都会判断事件句柄)
现在回过头来看前面所提到的addEventListener的第三个参数,实际上就是控制绑定函数在哪个阶段执行的,这个参数有两个选择,默认为false ,即冒泡阶段执行,如果设置为true则捕获阶段执行。
把上面的js代码改一下
<script>
function test1() {
console.log("1");
}
function test2() {
console.log("2");
}
function test3() {
console.log("3");
}
var oDiv=document.getElementsByTagName("div");
oDiv[0].addEventListener("click",test1,true);
oDiv[1].addEventListener("click",test2,true);
oDiv[2].addEventListener("click",test3,true);
oDiv[0].addEventListener("click",test1,false);
oDiv[1].addEventListener("click",test2,false);
oDiv[2].addEventListener("click",test3,false);
</script>
这时候我们点击最内层的<div>
打印出的是’1’ ‘2’ ‘3’ ‘3’ ‘2’ ‘1’
由此我们也可很清楚的看到,先进行事件捕获,设置为true的在捕获阶段便执行函数,之后是事件冒泡,设置为false的冒泡阶段执行,为了更直观你也可以写成这样
<script>
function test1() {
console.log("外层捕获");
}
function test2() {
console.log("中层捕获");
}
function test3() {
console.log("内层捕获");
}
function test4() {
console.log("外层冒泡");
}
function test5() {
console.log("中层冒泡");
}
function test6() {
console.log("内层冒泡");
}
var oDiv=document.getElementsByTagName("div");
oDiv[0].addEventListener("click",test1,true);
oDiv[1].addEventListener("click",test2,true);
oDiv[2].addEventListener("click",test3,true);
oDiv[0].addEventListener("click",test4,false);
oDiv[1].addEventListener("click",test5,false);
oDiv[2].addEventListener("click",test6,false);
</script>
就可以很清楚的看到事件的传递顺序了
合理运用事件冒泡可以实现事件委托,极大的节省资源。