刚接触 JS 的那个时候,啥也不懂,只想着如何利用 Google、百度到的函数来解决实际的问题,不会想到去一探究竟。
![图片来源于网络 图片来源网上](https://ww2.sinaimg.cn/mw690/e3dde130gw1f8u9zwqsi6j20zk0kfakm.jpg)
渐渐的,对 JS 的语言的不断深入,有机会去了解一些原理性东西。最近在看 JQuery 源码,感触很多,总想着用原生的 JS 去实现自己的一个 JQuery 库。说实在的,JQuery 里面很多函数和思路,是千百开源工作者长期的贡献,哪能是短时间就能消化的了。
最近再次碰到 addEventListener
函数(MDN 上关于 addEventListener 的介绍,很详细),由于之前并没有弄懂第三个参数的含义,要么默认值,要么手动设置成 false。这次看了不少文章,彻底把事件冒泡和捕获弄懂。
什么事件冒泡与捕获
事件冒泡与捕获是 DOM 中事件传播的两种方式,比如说对于注册了相同事件的两个 DOM 元素(简单点就是两个 div,一里一外),当点击里层 div 的时候,这两个事件谁先执行。
冒泡事件,由里向外,最里层的元素先执行,然后冒泡到外层。
捕获事件,由外向里,最外层的元素先执行,然后传递到内部。
在 IE 9 之前是只支持事件冒泡,IE 9(包括 IE 9) 之后和目前主流的浏览器都同时支持两种事件。
如何设置,只需修改 addEventListener
的第三个参数,true 为捕获,false 为冒泡,默认为冒泡。
举个简单的例子,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| <div>
<span class="out">
<span class="in"></span>
</span>
</div>
<script type="text/javascript">
var dom_out = document.getElementsByClassName('out')[0];
var dom_in = document.getElementsByClassName('in')[0];
dom_out.addEventListener('click',function(){
alert('out');
},false);
dom_in.addEventListener('click',function(){
alert('in');
},false);
</script>
|
outin
在上面这个例子中,事件是按照冒泡来执行的,点击里层的 in
,会看到先 alert
的顺序是先 “in” 后 “out”,如果把事件改成捕获,alert
的顺序又不一样了。
1
2
3
4
5
6
7
8
9
10
| <script type="text/javascript">
var dom_out = document.getElementsByClassName('out')[0];
var dom_in = document.getElementsByClassName('in')[0];
dom_out.addEventListener('click',function(){
alert('out');
},true);
dom_in.addEventListener('click',function(){
alert('in');
},true);
</script>
|
out2in2
上面这个例子是捕获事件的例子,点击 in
效果是不是不一样呢?
之所以会有冒泡和捕获事件(像 IE 9 之前的浏览器不支持捕获事件,还真是反程序员),毕竟在实际中处理事情肯定有个先后顺序,要么由里向外,要么由外向里,两者都是必须的。
但有时候为了兼容 IE 9 以下版本的浏览器,都会把第三个参数设置成 false 或者默认(默认就是 false)。
进一步理解冒泡和捕获
现在已经说清楚冒泡和捕获,那么如果同时出现冒泡和捕获会出现什么结果?
原来浏览器处理时间分为两个阶段,捕获阶段和冒泡阶段,
- 先执行捕获阶段,如果事件是在捕获阶段执行的(true 情况),则执行;
- 然后是冒泡阶段,如果事件是在冒泡阶段执行的(false 情况),则执行;
来看一看例子就知道了:
1
2
3
4
5
6
7
8
| <div>
<span class="s1">s1
<span class="s2">s2
<span class="s3">s3
</span>
</span>
</span>
</div>
|
这次我们设置三个 span,分别是 s1, s2, s3,然后设置 s1,s3 为冒泡执行,s2 为捕获执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <script type="text/javascript">
var s1 = document.getElementsByClassName('s1')[0];
var s2 = document.getElementsByClassName('s2')[0];
var s3 = document.getElementsByClassName('s3')[0];
s1.addEventListener('click',function(){
alert('s1');
},false);
s2.addEventListener('click',function(){
alert('s2');
},true);
s3.addEventListener('click',function(){
alert('s3');
},false);
</script>
|
s1s2s3
从运行的效果来看,点击 s3,依次 alert
s2 => s3 => s1,说明:
- 捕获事件和冒泡事件同时存在的,而且捕获事件先执行,冒泡事件后执行;
- 如果元素存在事件且事件的执行时间与当前逻辑一致(冒泡或捕获),则执行。
默认事件取消与停止冒泡
当然,有时候我们只想执行最内层或最外层的事件,根据内外层关系来把范围更广的事件取消掉(对于新手来说,不取消冒泡,很容易中招的出现 bug)。event.stopPropagation()
(IE 中window.event.cancelBubble = true
)可以用来取消事件冒泡。
有时候对于浏览器的默认事件也需要取消,这时候用到的函数则是 event.preventDefault()
(IE 中window.event.returnValue = false
)。
那么默认事件取消和停止冒泡有什么区别呢?我的理解:浏览器的默认事件是指浏览器自己的事件(这不废话吗),比如 a 标签
的点击,表单的提交等,取消掉就不会执行啦;冒泡则取消的是由外向里(捕获)、由里向外(冒泡),stop 之后,就不会继续遍历了。stackoverflow 上的解答
看下例子,依旧是上面那个例子,不过每个函数都加了 停止冒泡:
1
2
3
4
5
6
7
8
9
10
11
12
| s1.addEventListener('click',function(e){
e.stopPropagation();
alert('s1');
},false);
s2.addEventListener('click',function(e){
e.stopPropagation();
alert('s2');
},true);
s3.addEventListener('click',function(e){
e.stopPropagation();
alert('s3');
},false);
|
s1s2s3
点击的结果是:当点击 s2 或 s3 的时候,都会 alert
s2,点击 s1,弹出 s1。因为事件被取消的缘故,点击 s3,执行 s2后就不会在向下执行了。
在看一个 preventDefault
的例子。
1
2
3
4
5
6
7
8
9
10
11
12
| <div>
<a href="/">点我回主页</a>
</div>
<div>
<a href="/" class="back">点我不回主页</a>
</div>
<script type="text/javascript">
var back = document.getElementsByClassName('back')[0];
back.addEventListener('click', function(e){
e.preventDefault();
});
</script>
|
第二个链接是不是回不了主页,因为浏览器的默认事件被取消了。
以上所有例子请在非低版本 IE 浏览器的环境下浏览 O_o
总结
总结就补充两个兼容 IE 的函数吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| function stopBubble(e) {
if ( e && e.stopPropagation )
e.stopPropagation();
else
window.event.cancelBubble = true;
}
function stopDefault( e ) {
if ( e && e.preventDefault )
e.preventDefault();
else
window.event.returnValue = false;
return false;
}
|
共勉!
参考
stackoverflow 什么是事件冒泡和捕捉
stackoverflow stopPropagation 和 preventDefault 的区别
MDN addEventListener
javascript阻止事件冒泡和浏览器的默认行为