上个星期在微博中一个关于JavaScript捕获和冒泡代码的讨论,可能没有动手实现一篇的人无法给出确定的答案。
这里再来回顾一下之前的三条微博。
事件的执行顺序
JavaScript冒泡和捕获考察题目看图回答问题,晚上公布答案。
问:点击Li,js的执行顺序是什么?
总结就是:先捕获,后冒泡,捕获从上到下,冒泡从下到上(形象点说法:捕获像石头沉入海底,冒泡则像气泡冒出水面)
问:假如去掉注释 event.stopPropagation(); 结果又会输出什么?
答:去掉event.stopPropagation() 之后,由于事件有捕获和冒泡时先执行捕获,捕获到div之后,事件被阻止,后面就不在继续传播了。所以只输出divcallback.
在上面的代码如果增加一个div.addEventListener(‘click’,callbackdiv2,false);
则div先执行捕获,接着执行上面这句冒泡,所以微博里的一个朋友评论说事件执行过捕获就不会执行冒泡其实是不对的。
div.addEventListener(‘click’,callbackdiv,true);这一句只能是捕获才执行。
以上代码:演示
通过以上我们可知,event.stopPropagation();可以阻止捕获和冒泡阶段中当前事件的进一步传播。
onclick事件
那么onclick事件又会发生在哪个阶段呢?我试验了一下这个演示
由此可得onclick事件发生在冒泡阶段。
分享下面这篇文章的测试,我发现的另一个结论,如果对一个没有子元素的元素同时绑定冒泡和捕获,结果执行事件是遵循Javascript的执行顺序。如果有子元素,则是先执行捕获,然后再执行冒泡。
参考:https://github.com/zhukejin/blogs/blob/master/JAVASCRIPT/Javascript-event.md
引文:偶然和朋友讨论了事件绑定冒泡和捕获的执行顺序,各持己见,久争不下。 于是实验一番
此为 HTML 代码:
<body>
<ul class="ulclass">
<li class="li1class">1</li>
</ul>
</body>
实验一:
<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” 。
猜想一
- 在冒泡事件和捕获事件同时存在的情况下,捕获事件优先级高一点。
为了验证这个猜想,再来做几个实验:
实验二
<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”。
实验四
再来试一下两个事件都冒泡的情况:
//冒泡
$El.addEventListener("click",function(){
alert("ul");
},false);
//冒泡
$El1.addEventListener("click",function(){
alert("li");
},false);
实验分析:如果两个都为冒泡的情况下,点击后应从最里面的元素往外冒,也就是先弹出 li , 再弹出 ul.
实验结果:无误。
然后将两个事件都改成捕获,则先弹出 ul 再 弹出 li ,与预期完全一样。
实验五
这里把本来在 ul 上绑定的事件放到 li 上,也就是 li 绑定两个事件,一个为冒泡,一个为捕获。
var $El = document.querySelector(".ulclass");
var $El1 = document.querySelector(".li1class");
//冒泡
$El1.addEventListener("click",function(){
alert("冒泡");
},false);
//捕获
$El1.addEventListener("click",function(){
alert("捕获");
},true);
实验分析:按照之前的猜测,应该是先弹出捕获,然后弹出冒泡。(如果是这样的话我也不会写这么多了…) 实验结果:先弹出了 冒泡,然后弹出了捕获。
为什么??
猜想二
- 在同一个元素的绑定事件中,冒泡和捕获没有次序之分,遵循Javascript的执行顺序。
实验六
为了进一步证实这个猜测…我们将冒泡和捕获的位置调换一下
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执行顺序触发。
附:
冒泡经常用于需要绑定很多事件的时候,给他们父级元素绑定一个事件,可以有效的提高代码执行效率。 比如:
<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 上绑定一个事件:
<script type="text/javascript">
var $El = document.querySelector(".ulclass");
//冒泡
$El.addEventListener("click",function(){
console.log(event.target);
},false);
</script>
其中 event.target 就是获取触发来源。 it’ OK。
事件不同浏览器处理函数
- element.addEventListener(type, listener[, useCapture]); // IE6~8不支持(捕获和冒泡通过useCapture,默认false)
- element.attachEvent(’on’ + type, listener); // IE6~10,IE11不支持(只执行冒泡事件)
- element[’on’ + type] = function(){} // 所有浏览器(默认执行冒泡事件)
W3C规范中定义了3个事件阶段,依次是捕获阶段、目标阶段、冒泡阶段。事件对象按照上图的传播路径依次完成这些阶段。如果某个阶段不支持或事件对象的传播被终止,那么该阶段就会被跳过。举个例子,如果Event.bubbles
属性被设置为false
,那么冒泡阶段就会被跳过。如果Event.stopPropagation()
在事件派发前被调用,那么所有的阶段都会被跳过。
- 捕获 阶段:在事件对象到达事件目标之前,事件对象必须从window经过目标的祖先节点传播到事件目标。 这个阶段被我们称之为捕获阶段。在这个阶段注册的事件监听器在事件到达其目标前必须先处理事件。
- 目标 阶段:事件对象到达其事件目标。 这个阶段被我们称为目标阶段。一旦事件对象到达事件目标,该阶段的事件监听器就要对它进行处理。如果一个事件对象类型被标志为不能冒泡。那么对应的事件对象在到达此阶段时就会终止传播。
- 冒泡 阶段: 事件对象以一个与捕获阶段相反的方向从事件目标传播经过其祖先节点传播到window。这个阶段被称之为冒泡阶段。在此阶段注册的事件监听器会对相应的冒泡事件进行处理。
在一个事件完成了所有阶段的传播路径后,它的Event.currentTarget
会被设置为null
并且Event.eventPhase
会被设为0。Event
的所有其他属性都不会改变(包括指向事件目标的Event.target
属性).
跨浏览器的事件处理函数:
var EventUtil = { addHandler: function(element, type, handler) { if (element.addEventListener) { // DOM2 element.addEventListener(type, handler, false); } else if (element.attachEvent) { // IE element.attachEvent('on' + type, handler); } else { // DOM0 element['on' + type] = handler; } }, removeHandler: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent('on' + type, handler); } else { element['on' + type] = null; } } };
跨浏览器的事件对象:
var EventUtil = { getEvent: function(e) { return e ? e : window.e; }, getTarget: function(e) { return e.target || e.srcElement; }, preventDefault: function(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } }, stopPropagation: function(e) { if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true; } } }
可以看我之前写过的一篇文章:如何停止冒泡和阻止默认行为
ps:半个月没发文章,因为我最近在搞这个:开发头条网,然后我发现这个东西做好和做坏根本没有出路,我考虑要不要下掉,呜呜……,请赐予我动力吧。
W3c明智的在这场争斗中选择了一个择中的方案。任何发生在w3c事件模型中的事件,首是进入捕获阶段,直到达到目标元素,再进入冒泡阶段
为一个web开发者,你可以选择是在捕获阶段还是冒泡阶段绑定事件处理函数,这是通过addEventListener()方法实现的,如果这个函数的最后一个参数是true,则在捕获阶段绑定函数,反之false,在冒泡阶段绑定函数。