Javascript事件流

本章节主要包括以下四个方面:

  • 什么是事件流
  • 事件冒泡
  • 事件捕获
  • W3C事件流(DOM事件流)

什么是事件流

定义
1.事件流描述的是从页面中接收事件的顺序,也可理解为事件在页面中传播的顺序。

2.事件就是用户或浏览器自身执行的某种动作。诸如click(点击)、load(加载)、mouseover(鼠标悬停)。

3.事件处理程序响应某个事件的函数就叫事件处理程序(或事件侦听器)。

JS事件流最早要从IE和网景公司的浏览器大战说起,IE提出的是冒泡流,而网景提出的是捕获流,后来在W3C组织的统一之下,JS支持了冒泡流和捕获流,但是目前低版本的IE浏览器还是只能支持冒泡流(IE6,IE7,IE8均只支持冒泡流),所以为了能够兼容更多的浏览器,建议大家使用冒泡流。

事件冒泡

微软提出了名为事件冒泡的事件流。事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象,事件的传递应该是:p -> div -> body -> html -> document

事件捕获

网景提出另一种事件流名为事件捕获与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。事件捕获顺序应该是:document -> html -> body -> div -> p

W3C事件流(DOM事件流)

当一个DOM事件被触发的时候,他并不是只在它的起源对象上触发一次,而是会经历三个不同的阶段。简而言之:事件一开始从文档的根节点流向目标对象(捕获阶段),然后在目标对向上被触发(目标阶段),之后再回溯到文档的根节点(冒泡阶段)如图所示(图片来自W3C):

事件捕获阶段(Capture Phase)

事件从文档的根节点出发,随着DOM树的结构向事件的目标节点流去。途中经过各个层次的DOM节点,并在各节点上触发捕获事件,直到到达时间的目标节点。捕获阶段的主要任务是简历传播路径,在冒泡阶段,时间会通过这个路径回溯到文档根节点。

例如,通过下面的这个函数来给节点设置监听,可以通过将;设置成true来为事件的捕获阶段添加监听回调函数。

1
element.removeEventListener(&ltevent-name>, <callback>, <use-capture>);

而,在实际应用中,我们并没有太多使用捕获阶段监听的用例,但是通过在捕获阶段对事件的处理,我们可以阻止类似click事件在某个特定元素上被触发。如下:

1
2
3
4
var form=document.querySeletor('form');
form.addEventListener('click',function(e){
e.stopPropagation();
},true);

如果你对这种用法不是很了解的话,建议设置为false或者undefined,从而在冒泡阶段对事件进行监听,这也是常用的方法。

目标阶段(Target Phase)

当事件到达目标节点时,事件就进入了目标阶段。事件在目标节点上被触发,然后逆向回流,直到传播到最外层的文档节点。

对于多层嵌套的节点,鼠标和指针事件经常会被定位到最里层的元素上。假设,你在一个div元素上设置了click的监听函数,而用户点击在了这个div元素内部的p元素上,那么p元素就是这个时间的目标元素。事件冒泡让我们可以在这个div或者更上层的元素上监听click事件,并且时间传播过程中触发回调函数。

冒泡阶段(Bubble Phase)

事件在目标事件上触发后,并不在这个元素上终止。它会随着DOM树一层层向上冒泡,直到到达最外层的根节点,一直向上传播,直到document对象。也就是说,同一事件会一次在目标节点的父节点,父节点的父节点…直到最外层的节点上触发。

绝大多数事件是会冒泡的,但并非所有的。

示例代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<head>
<meta charset='utf-8' />
<style>
#wrap {
width: 200px;
height: 200px;
background: orange;
}
#outer {
position: relative;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
background: #eeddff;
}
#inner {
position: relative;
top: 25px;
left:25px;
width: 50px;
height: 50px;
background: #44ddff;
}
</style>
</head>
<body>
<div id="wrap">
<div id="outer">
<div id="inner"></div>
</div>
</div>
</body>

<script>
var wrap = document.getElementById('wrap');
var outet = document.getElementById('outer');
var inner = document.getElementById('inner');

wrap.addEventListener('click',function(){
alert('789');
},false);
outer.addEventListener('click',function(){
alert('456');
},false);
inner.addEventListener('click',function(){
alert('123');
},false);
wrap.addEventListener('click',function(){
alert('wrap');
},true);
outer.addEventListener('click',function(){
alert('outer');
},true);
inner.addEventListener('click',function(){
alert('inner');
},true);
</script>
</html>

结论:当点击页面中心浅蓝色部分的时候,先从最不具体的节点捕获事件,先弹出wrap,接着弹出outer。接着处于目标阶段,先弹出123,再弹出inner。紧接着,事件处于冒泡阶段,先弹出456,再弹出789。因此我们可以得出结论,当容器元素及嵌套元素,即在捕获阶段又在冒泡阶段调用事件处理程序时:事件按DOM事件流的顺序执行事件处理程序,当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序。

提示:

  • 所有现代浏览器都支持事件冒泡,但在具体实现中略有差别
  • 由于老版本浏览器不支持使用事件捕获。建议使用事件冒泡
  • 尽管“DOM2级事件”标准规范明确规定事件捕获阶段不会涉及事件目标,但是在IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两次机会在目标对象上面操作事件。
  • 并非所有的事件都会经过冒泡阶段 。所有的事件都要经过捕获阶段和处于目标阶段,但是有些事件会跳过冒泡阶段:如,获得输入焦点的focus事件和失去输入焦点的blur事件。

##