(一) DOM事件流
DOM事件流个人感觉就是事件冒泡和事件捕获的结合体。
DOM事件流:将事件分为三个阶段:捕获阶段、目标阶段、冒泡阶段。先调用捕获阶段的处理函数,其次调用目标阶段的处理函数,最后调用冒泡阶段的处理函数。这个过程很类似于Struts2框中的action和Interceptor。当发出一个URL请求的时候,先调用前置拦截器,其次调用action,最后调用后置拦截器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<script>
window.onload =
function
(){
var
outA = document.getElementById(
"outA"
);
var
outB = document.getElementById(
"outB"
);
var
outC = document.getElementById(
"outC"
);
// 目标(自身触发事件,是冒泡还是捕获无所谓)
outC.addEventListener(
'click'
,
function
(){alert(
"target"
);},
true
);
// 事件冒泡
outA.addEventListener(
'click'
,
function
(){alert(
"bubble1"
);},
false
);
outB.addEventListener(
'click'
,
function
(){alert(
"bubble2"
);},
false
);
// 事件捕获
outA.addEventListener(
'click'
,
function
(){alert(
"capture1"
);},
true
);
outB.addEventListener(
'click'
,
function
(){alert(
"capture2"
);},
true
);
};
</script>
<body>
<div id=
"outA"
style=
"width:400px; height:400px; background:#CDC9C9;position:relative;"
>
<div id=
"outB"
style=
"height:200; background:#0000ff;top:100px;position:relative;"
>
<div id=
"outC"
style=
"height:100px; background:#FFB90F;top:50px;position:relative;"
></div>
</div>
</div>
</body>
|
当点击outC的时候,依次打印出capture1-->capture2-->target-->bubble2-->bubble1。到这里是不是可以理解addEventListener(type,handler,useCapture)这个API中第三个参数useCapture的含义呢?useCapture=false意味着:将事件处理函数加入到冒泡阶段,在冒泡阶段会被调用;useCapture=true意味着:将事件处理函数加入到捕获阶段,在捕获阶段会被调用。从DOM事件流模型可以看出,捕获阶段的事件处理函数,一定比冒泡阶段的事件处理函数先执行。
(二) 再谈事件函数执行先后顺序
在DOM事件流中提到过:
// 目标(自身触发事件,是冒泡还是捕获无所谓)
outC.addEventListener('click',function(){alert("target");},true);
我们在outC上触发onclick事件(这个是目标对象),如果我们在outC上同时绑定捕获阶段/冒泡阶段事件处理函数会怎么样呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<script>
window.onload =
function
(){
var
outA = document.getElementById(
"outA"
);
var
outB = document.getElementById(
"outB"
);
var
outC = document.getElementById(
"outC"
);
// 目标(自身触发事件,是冒泡还是捕获无所谓)
outC.addEventListener(
'click'
,
function
(){alert(
"target2"
);},
true
);
outC.addEventListener(
'click'
,
function
(){alert(
"target1"
);},
true
);
// 事件冒泡
outA.addEventListener(
'click'
,
function
(){alert(
"bubble1"
);},
false
);
outB.addEventListener(
'click'
,
function
(){alert(
"bubble2"
);},
false
);
// 事件捕获
outA.addEventListener(
'click'
,
function
(){alert(
"capture1"
);},
true
);
outB.addEventListener(
'click'
,
function
(){alert(
"capture2"
);},
true
);
};
</script>
<body>
<div id=
"outA"
style=
"width:400px; height:400px; background:#CDC9C9;position:relative;"
>
<div id=
"outB"
style=
"height:200; background:#0000ff;top:100px;position:relative;"
>
<div id=
"outC"
style=
"height:100px; background:#FFB90F;top:50px;position:relative;"
></div>
</div>
</div>
</body>
|
点击outC的时候,打印顺序是:capture1-->capture2-->target2-->target1-->bubble2-->bubble1。由于outC是我们触发事件的目标对象,在outC上注册的事件处理函数,属于DOM事件流中的目标阶段。目标阶段函数的执行顺序:先注册的先执行,后注册的后执行。这就是上面我们说的,在目标对象上绑定的函数是采用捕获,还是采用冒泡,都没有什么关系,因为冒泡和捕获只是对父元素上的函数执行顺序有影响,对自己没有什么影响。如果不信,可以将下面的代码放进去验证。
1
2
3
4
5
|
// 目标(自身触发事件,是冒泡还是捕获无所谓)
outC.addEventListener(
'click'
,
function
(){alert(
"target1"
);},
false
);
outC.addEventListener(
'click'
,
function
(){alert(
"target2"
);},
true
);
outC.addEventListener(
'click'
,
function
(){alert(
"target3"
);},
true
);
outC.addEventListener(
'click'
,
function
(){alert(
"target4"
);},
false
);
|
至此我们可以给出事件函数执行顺序的结论了:捕获阶段的处理函数最先执行,其次是目标阶段的处理函数,最后是冒泡阶段的处理函数。目标阶段的处理函数,先注册的先执行,后注册的后执行。
(三) 阻止事件冒泡和捕获
默认情况下,多个事件处理函数会按照DOM事件流模型中的顺序执行。如果子元素上发生某个事件,不需要执行父元素上注册的事件处理函数,那么我们可以停止捕获和冒泡,避免没有意义的函数调用。前面提到的5种事件绑定方式,都可以实现阻止事件的传播。由于第5种方式,是最推荐的做法。所以我们基于第5种方式,看看如何阻止事件的传播行为。IE8以及以前可以通过 window.event.cancelBubble=true阻止事件的继续传播;IE9+/FF/Chrome通过event.stopPropagation()阻止事件的继续传播。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<script>
window.onload =
function
(){
var
outA = document.getElementById(
"outA"
);
var
outB = document.getElementById(
"outB"
);
var
outC = document.getElementById(
"outC"
);
// 目标
outC.addEventListener(
'click'
,
function
(event){
alert(
"target"
);
event.stopPropagation();
},
false
);
// 事件冒泡
outA.addEventListener(
'click'
,
function
(){alert(
"bubble"
);},
false
);
// 事件捕获
outA.addEventListener(
'click'
,
function
(){alert(
"capture"
);},
true
);
};
</script>
<body>
<div id=
"outA"
style=
"width:400px; height:400px; background:#CDC9C9;position:relative;"
>
<div id=
"outB"
style=
"height:200; background:#0000ff;top:100px;position:relative;"
>
<div id=
"outC"
style=
"height:100px; background:#FFB90F;top:50px;position:relative;"
></div>
</div>
</div>
</body>
|
当点击outC的时候,之后打印出capture-->target,不会打印出bubble。因为当事件传播到outC上的处理函数时,通过stopPropagation阻止了事件的继续传播,所以不会继续传播到冒泡阶段。
最后再看一段更有意思的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<script>
window.onload =
function
(){
var
outA = document.getElementById(
"outA"
);
var
outB = document.getElementById(
"outB"
);
var
outC = document.getElementById(
"outC"
);
// 目标
outC.addEventListener(
'click'
,
function
(event){alert(
"target"
);},
false
);
// 事件冒泡
outA.addEventListener(
'click'
,
function
(){alert(
"bubble"
);},
false
);
// 事件捕获
outA.addEventListener(
'click'
,
function
(){alert(
"capture"
);event.stopPropagation();},
true
);
};
</script>
<body>
<div id=
"outA"
style=
"width:400px; height:400px; background:#CDC9C9;position:relative;"
>
<div id=
"outB"
style=
"height:200; background:#0000ff;top:100px;position:relative;"
>
<div id=
"outC"
style=
"height:100px; background:#FFB90F;top:50px;position:relative;"
></div>
</div>
</div>
</body>
|
执行结果是只打印capture,不会打印target和bubble。神奇吧,我们点击了outC,但是却没有触发outC上的事件处理函数,而是触发了outA上的事件处理函数。