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