0%

Javascript 事件冒泡和捕获的一些探讨

本文为笔者实验总结
如有不正,请指点。

引文:偶然和朋友讨论了事件绑定冒泡和捕获的执行顺序,各持己见,久争不下。

于是实验一番_

此为 HTML 代码:

1
2
3
4
5
<body>
<ul class="ulclass">
<li class="li1class">1</li>
</ul>
</body>

实验一:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
var $El = document.querySelector(".ulclass");
var $El1 = document.querySelector(".li1class");

//冒泡
$El.addEventListener("click",function(){
alert("ul");
},false);
//捕获
$El1.addEventListener("click",function(){
alert("li");
},true);
</script>

实验分析:冒泡执行过程中是从最内层元素 li 开始往外冒泡,在 经过 li 一层元素后到达 ul 被触发。而捕获过程则一共经历 window-> document -> body -> ul 四层元素后到达 li ,被触发,看起来好像是应该先弹出 ul ,然后再弹出 li。

实验结果:点击 li 内的 1 的时候,第一次弹出 “li”,第二次弹出 “ul” 。

猜想一

  • 在冒泡事件和捕获事件同时存在的情况下,捕获事件优先级高一点

为了验证这个猜想,再来做几个实验:

实验二

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
var $El = document.querySelector(".ulclass");
var $El1 = document.querySelector(".li1class");
//捕获
$El.addEventListener("click",function(){
alert("ul");
},true);

//冒泡
$El1.addEventListener("click",function(){
alert("li");
},false);
</script>

实验分析:如果上述猜想是正确的,那么这次的执行结果就应该是先弹出 “ul” 再弹出 “li”。

实验结果:先弹出 “ul” 再弹出 “li”。

实验四

再来试一下两个事件都冒泡的情况:

1
2
3
4
5
6
7
8
9
//冒泡
$El.addEventListener("click",function(){
alert("ul");
},false);

//冒泡
$El1.addEventListener("click",function(){
alert("li");
},false);

实验分析:如果两个都为冒泡的情况下,点击后应从最里面的元素往外冒,也就是先弹出 li , 再弹出 ul.

实验结果:无误。

然后将两个事件都改成捕获,则先弹出 ul 再 弹出 li ,与预期完全一样。

实验五

这里把本来在 ul 上绑定的事件放到 li 上,也就是 li 绑定两个事件,一个为冒泡,一个为捕获。

1
2
3
4
5
6
7
8
9
10
11
var $El = document.querySelector(".ulclass");
var $El1 = document.querySelector(".li1class");
//冒泡
$El1.addEventListener("click",function(){
alert("冒泡");
},false);

//捕获
$El1.addEventListener("click",function(){
alert("捕获");
},true);

实验分析:按照之前的猜测,应该是先弹出捕获,然后弹出冒泡。(如果是这样的话我也不会写这么多了…)
实验结果:先弹出了 冒泡,然后弹出了捕获。

为什么??

猜想二

  • 在同一个元素的绑定事件中,冒泡和捕获没有次序之分,遵循Javascript的执行顺序。

实验六

为了进一步证实这个猜测…我们将冒泡和捕获的位置调换一下

1
2
3
4
5
6
7
8
9
10
11
var $El = document.querySelector(".ulclass");
var $El1 = document.querySelector(".li1class");
//捕获
$El1.addEventListener("click",function(){
alert("捕获");
},true);

//冒泡
$El1.addEventListener("click",function(){
alert("冒泡");
},false);

实验结果: 先捕获,后冒泡。

看似是这样了,但是在重复实验五 的时候,li下一级有一个元素 a ,直接点击 li 很符合上面的推测,但是在点击a元素的时候,先弹出了捕获。

为什么??

猜测三

  • 在元素上同时绑定捕获事件和冒泡事件,如果通过此元素的子级元素触发,则优先触发捕获事件,若不通过此元素的子级元素触发,则按照Javascript执行顺序触发。

附:

冒泡经常用于需要绑定很多事件的时候,给他们父级元素绑定一个事件,可以有效的提高代码执行效率。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<div class="">
<ul class="ulclass">
<li class="li1class">1</li>
<li class="li1class">1</li>
<li class="li1class">1</li>
...
...
<li class="li1class">1</li>
</ul>
</div>
</body>

假如此处有100个li,每个li元素上都绑定一个事件的话就验证影响了执行效率,那么就可以在ul 上绑定一个事件:

1
2
3
4
5
6
7
<script type="text/javascript">
var $El = document.querySelector(".ulclass");
//冒泡
$El.addEventListener("click",function(){
console.log(event.target);
},false);
</script>

其中 event.target 就是获取触发来源。
it’ OK。

附二:

经网友描述:

###事件传播三阶段:

  1. capture phase (事件捕获)
  2. target phase (处于目标)
  3. bubbling phase (事件冒泡)

在 target phase,event handler 被调用的顺序不再遵循先捕获,后冒泡的原则,而是严格按照 event handler 注册的顺序,先注册,故而先执行。要改变二者的执行顺序,只需要改变两段源码的顺序即可。

W3C 规范:

Next, the implementation must determine the current target’s candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration. [HTML5] defines the ordering of listeners registered through event handler attributes. Once determined, the candidate event listeners must not be changed. Adding or removing listeners does not affect the current target’s candidate event listeners.

见:

https://stackoverflow.com/questions/18103391/usecapture-in-addeventlistener-behaves-strangely?lq=1

https://stackoverflow.com/questions/11841330/event-listeners-registered-for-capturing-phase-not-triggered-before-bubbling-w

欢迎指正。