事件流之冒泡和捕获

事件流

有时候我们在网页中多个地方都绑定了点击事件,但是当你点击其中一个,你会发现其他的绑定事件也被触发了,是不是觉得有点灵异,实际上是浏览器的事件流在作怪。

假设我们在页面上有一个按钮,那么当我们单击它时,你们是不是认为单击事件仅仅只会发生在这个按钮上?这其实只是我们理所当然的认为而已。想象一下,现在有一张画了很多个同心圆的纸,你把手指放在圆心上面,那么你的手就指向了最小的圆吗,不,事实并不是这样,你其实指向了纸上的所有圆。

在浏览器页面上,众多浏览器开发团队也认为你在单击按钮的同时,也单击了包含按钮的容器元素,甚至也单击了整个页面

很奇葩的是IE和Netscape开发团队提出了两种相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流

事件冒泡

IE的事件流叫做事件冒泡,即开始时由最具体的元素(文档中嵌套层次最深的那个节点),通常也是我们点击的节点接收,然后逐级向上传播。

别的啥都不说,看代码说话

EXP1

1
2
3
4
5
6
7
8
<!Doctype htm>
<head>
<title>事件冒泡</title>
</head>
<body>
<div id="click">点我啊!</div>
</body>
</html>

现在,当你点击了div元素时,传播路径是这样的:


  1. div

  2. body

  3. html

  4. document


看到没有,原来你不止触发了一个点击事件,只要你在body或者html上绑定一个事件,你会发现,他们都会在div事件触发之后被触发。现代浏览器都支持事件冒泡,只是有少许差别。IE9、FF、chrome、Safari会将事件一直冒泡到window对象,不过我们不用太在意。

下面在上面的代码基础上我们再做一些修改

EXP2

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
<!Doctype htm>
<head>
<title>事件冒泡</title>
<style>
#ancestor{width:250px;height:200px;background:orange;}
#parent{width:150px;height:100px;background:skyblue;}
#child{width:50px;height:50px;background:yellow;}
</style>
</head>
<body>
<div id="ancestor">
<div id="parent">
<div id="child"></div>
</div>
</div>
<script>
var aObj = document.getElementById("ancestor");
var pObj = document.getElementById("parent");
var cObj = document.getElementById("child");
var clickFunc = function(e){
console.log("my id is "+this.id);
}
aObj.onclick = clickFunc;
pObj.onclick = clickFunc;
cObj.onclick = clickFunc;
</script>
</body>
</html>

但我们点击最小的区域块,即黄色的child,效果图如下:

event1

我们只点击了child子元素,但是却也触发了parent和ancestor的点击事件!

取消冒泡

有些情况下,我们并不希望子元素的事件影响父元素,这时我们就要阻止冒泡事件了,具体兼容代码如下:

1
2
3
4
5
6
7
8
function stopBubble(e){
var e = e || window.event;
if(e && e.stopPropagation){
e.stopPropagation();
}else(
e.cancleBubble = true//仅IE6/7/8支持
)
}

下面我们再看看事件捕获又是怎么一回事。

事件捕获

事件捕获是由Netscape提出的另一种事件流,它与冒泡恰好相反,是由顶层至上而下传播的。

如果在捕获状态下触发第一个例子的话,传播路径则相反过来了:


  1. document

  2. html

  3. body

  4. div

document事件首先接受到click事件,然后沿DOM树依次向下一直传递到到我们所点击的div元素,至此,捕获事件才算完成。

我们对例二进行一下修改,来看看捕获的实际过程。

EXP3

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
<!Doctype htm>
<head>
<title>事件捕获</title>
<style>
#ancestor{width:250px;height:200px;background:orange;}
#parent{width:150px;height:100px;background:skyblue;}
#child{width:50px;height:50px;background:yellow;}
</style>
</head>
<body>
<div id="ancestor">
<div id="parent">
<div id="child"></div>
</div>
</div>
<script>
var aObj = document.getElementById("ancestor");
var pObj = document.getElementById("parent");
var cObj = document.getElementById("child");
var clickFunc = function(e){
console.log("my id is "+this.id);
}
aObj.addEventListener("click",clickFunc,true);
pObj.addEventListener("click",clickFunc,true);
cObj.addEventListener("click",clickFunc,true);
</script>
</body>
</html>

我们依旧点击黄色的child,效果如下:

event2

因为老版本浏览器不支持,所以并不建议使用它,除非特殊需要。

DOM事件流

任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段

默认情况下,执行的是时间冒泡。

那么我们要如何让事件在捕获或者冒泡时执行呢?

事件绑定方法

这里我们只涉及addEventListener、attachEvent这两个绑定方法。

  • addEventListener(event,func,useCapture)

    参数:

    event:事件名称,如click,不带on
    func:用于监听的函数
    useCapture:是否进行捕获,默认为false即冒泡
    

    addEventListener在IE11、Chrome、FF、Safiri等浏览器都支持

  • attachevent(event,func)

    参数:

    event:事件名称,如onclick,带on
    func:用于监听的函数
    

    attachEvent仅在IE10及以下才支持.

事件委托

说完了事件的冒泡、捕获和绑定方法,下面综合这些知识点来讲讲事件委托。

有时候我们需要给很多子元素添加事件,这时候我们可以选择另一种方式,把事件添加到父节点即将事件委托给父节点来触发处理函数。这主要得益于Dom事件流的事件冒泡机制。

假设我们有一个ul列表,需要给下面的子节点绑定函数:

1
2
3
4
5
6
7
8
<ul id="item-list">
<ll>item 1</li>
<ll>item 2</li>
<ll>item 3</li>
<ll>item 4</li>
<ll>item 5</li>
<ll>item 6</li>
</ul>

现在我们要求点击每个li元素的时候在console打印出他的content。最普遍的写法,就是给每个li元素通过onClick的方式绑定监听函数。

这样做不仅费力,当li元素的个数增加或者减少时,我们就需要手动为新添加的元素绑定函数。更好的方式是使用事件委托机制,把事件绑定到父元素或者更上层的节点,通过检查时间的目标对象(event.target)来获取事件源。

1
2
3
4
5
6
7
8
var clickFunc = function(e){
var e = e || window.event;
var target = e.target || e.srcElement;//IE兼容写法
if(target.tagName.toLowerCase() == "li"){
console.log(target.index);
}
}
document.getElementById("item-list").addEventListener("click",clickFunc);

我们通过e.target拿到被点击的L节点,除此之外e还有一个currentTarget属性,里面保存的是触发事件的当前对象,这里为ul元素。

掌握好这些事件流的知识点对我们今后的开发会很有帮助,大家可以上网多搜搜捕获冒泡及委托的例子学习下!