Capture和Bubble事件阶段

做过Web开发应该都知道addEventListener,它接受三个参数,前两个都很好懂,分别表示事件类型和事件处理器,而最后一个参数是 useCapture,就有点让费解了,它是boolean参数,为true时表示Capture阶段触发事件,为false表示在Bubble阶段触发 事件。之前一直没有怎么关注这个参数,对它只是一知半解,主要原因在于IE使用AttachEvent来注册事件处理器,但它不支持 useCapture,一律表示在冒泡阶段触发事件,所以为了兼容IE这位老大哥,addEventListener的useCapture通常设置为 false。如果使用更高级的类库,如jQuery,那么它已经帮你做好这些了。但我最近在做Firefox下的插件开发,我知道Firefox是支持 useCapture参数的,看着有些地方使用false,有些地方使用true,而我又不知道它们这么用的原因,这总令人不痛快,因此这才花时间将它弄 清楚,也正是这篇博客所要讲的。 

要搞清楚为什么要有Capture和Bubble两个阶段,实际上加上触发事件的元素(target)一共有三个阶段,首先得搞清楚事件的传播。 事件需要传播的原因在于事件的实际触发者很可能没有注册事件处理器,而只在它的父元素上注册了同一事件的处理器,这时事件就要传播到父元素。还是举例来得 明白,看下面的HTML文档: 
Html代码
  1. <html>  
  2. <body>  
  3.     <p id="the_p">This is a <a id="the_a" href="#"><span id="the_span">SAMPLE</span></a> text.</p>  
  4. </body>  
  5. <script>  
  6.     function clickHandler(event) {  
  7.         var eventInfo = "event target: " + event.target.tagName +  
  8.                 ", current target: " + event.currentTarget.tagName;  
  9.         alert(eventInfo);  
  10.     }  
  11.     document.getElementById('the_a').addEventListener('click', clickHandler, false);  
  12.     document.getElementById('the_p').addEventListener('click', clickHandler, false);  
  13. </script>  
  14. </html>  

在上面的例子中,我们在A和P元素上分别注册了同一个事件处理器,但并没有在SPAN元素上注册,当我们点击“SAMPLE”时,实际点击的 SPAN元素,那么是不是因为SPAN上没有事件处理器就忽略这个事件了呢?显然不是,因为我们在A和P上注册了处理器,对A和P来说,点击SPAN相当 于也点击了A和P,当然也点击了BODY和DOCUMENT,所以也要触发A和P上的事件处理器,也就是说事件需要从SPAN传播到A到P,然后到 BODY和DOCUMENT,每个被传播到的元素都有机会处理这个事件(注意:这里描述的传播顺序不准备,下面还会讲到)。 

每个事件处理器都接受一个event参数,它有相当多的属性,随着事件类型而定,但常见的也就那么几个,上面的例子使用了两个很重要的属 性,target表示事件的实际触发者,在这个例子中就是SPAN,currentTarget表示正在处理事件的元素,如果是A正在处理这个事件,那么 event.currentTarget就是A元素,如果P元素正在处理事件,那么event.currentTarget就是P元素。也就是说,对同一 个事件,event.target总是事件的触发者,而event.currentTarget视谁正在处理这个事件而 定,event.currentTarget总是等于this(在JavaScript中使用this总需要格外小心,因为JavaScript可以通过 call或apply改变this)。 


另外一个问题,既然A和P的事件处理器都会触发,那么那个会先触发呢?这就要涉及到事件传播的三个阶段,它们分别是捕获(Capture)阶段, 事件目标(Target)阶段和冒泡(Bubble)阶段。它们的顺序是Capture阶段最先,Target居中,Bubble阶段最后。文字描述太抽 像,依然拿上面的例子来说,当我们点击SPAN元素之后,click事件的传播会分成三个阶段: 
 Capture阶段:DOCUMENT -> BODY -> P -> A
 Target阶段:SPAN
 Bubble阶段:A -> P -> BODY -> DOCUMENT

我们可以看到Catpure阶段和Bubble阶段经历的元素顺序刚好相反。对每个事件,除了target元素以外,每个元素会被传播两次,但是 其事件处理器只能触发一次,到底在哪一次触发就取决于useCapture参数了,如果useCapture为true,表示在第一次传播到该元素时就触 发(即Capture阶段),否则表示第二次传播到该元素才触发(即Bubble阶段),对于target元素,由于它只会传播一次,所以指定 useCapture没有意义(但必须指定,因为该参数是必须的),它总是在传播的中间点触发(即Target阶段)。对上面的例子,由于A和P调用 addEventListener时useCapture均为false,所以它们都只在Bubble阶段触发。这样,在Capture阶段不会触发任何 事件处理器,因为SPAN上没有注册事件处理器,所以Target阶段也不会触发事件处理器,在Bubble阶段,会在A和P上触发事件处理器,但由于事 件先传播到A然后再传播到P,所以A的事件处理器先于P的事件处理器被触发。这可以从运行的結果看出来,先弹出的警示框显示current target为A,然后才弹出current target为P的警示框。 


现在将注册事件的过程改一下,变成都在Capture阶段触发,如下: 
Js代码
  1. document.getElementById('the_a').addEventListener('click', clickHandler, true);  
  2. document.getElementById('the_p').addEventListener('click', clickHandler, true);  

这样的结果会怎样呢?分析下事件传播过程,很快就能得到结论,两个事件处理器都在Capture阶段触发,而在Capture阶段,事件先传播到P然后再传播到A,所以P的事件处理器先于A的事件处理器被触发。改成下面的又会怎样? 
Js代码
  1. document.getElementById('the_a').addEventListener('click', clickHandler, false);  
  2. document.getElementById('the_p').addEventListener('click', clickHandler, true);  

也就是A的事件处理器在Bubble阶段触发,P的在Capture阶段触发,分析事件传播过程,由于Capture阶段总是先于Bubble阶段,所以P的事件处理器会先触发。 

现在我们可以看到事件传播所经过的元素形成了一个链条,对上面的例子,这个链条即是DOCUMENT -> BODY -> P -> A -> SPAN -> A -> P -> BODY -> DOCUMENT。那么这个传播链条是如何确定的呢?其实很简单,想必读者也能够猜想得到,在事件触发之时,首先确定target元素(这里是 SPAN),然后确定它的父元素(A),再找父元素的父元素(P),这样一直下去,直到找文档根元素(DOCUMENT),这相当于Bubble阶段的传 播过程,得到事件的整个传播过程,只需要将这个链条反过来并去掉target元素,添加原来链条的开头就得到事件的整个传播路径了。整个路径是在事件触发 之时就确定了,即使在事件传播过程删除了路径中的一些元素,注册在其上的事件处理器也依然会触发。看例子: 
Js代码
  1. function pHandler(event) {  
  2.     // remove A  
  3.     document.getElementById("the_p").removeChild(document.getElementById("the_a"));  
  4.     var eventInfo = "event target: " + event.target.tagName +  
  5.             ", current target: " + event.currentTarget.tagName;  
  6.     alert(eventInfo);  
  7. }  
  8. function aHandler(event) {  
  9.     var eventInfo = "event target: " + event.target.tagName +  
  10.             ", current target: " + event.currentTarget.tagName;  
  11.     alert(eventInfo);  
  12. }  
  13. document.getElementById('the_p').addEventListener('click', pHandler, true);  
  14. document.getElementById('the_a').addEventListener('click', aHandler, false);  

P事件处理器在Capture阶段触发而A的在Bubble阶段触发,因此P会先触发,它的事件处理器中会先删除A元素,然后显示事件信息。尽管 删除了A,它注册其上的事件处理器还是会触发,因为它的删除是在传播过程中删除的,而事件的传播路径是在事件触发之时就已经确定了。 

默认情况下事件传播会走完完整的三个阶段,但event的stopPropagation()可以中止事件的传播。 
Js代码
  1. function clickHandler(event) {  
  2.     var eventInfo = "event target: " + event.target.tagName +  
  3.             ", current target: " + event.currentTarget.tagName;  
  4.     alert(eventInfo);  
  5.     event.stopPropagation();  
  6. }  
  7.   
  8. document.getElementById('the_p').addEventListener('click', clickHandler, true);  
  9. document.getElementById('the_a').addEventListener('click', clickHandler, false);  

对上面的例子,会先在Capture阶段触发P的事件处理器,它调用了stopPropagation()方法来阻止事件的继续传播,因此A元素 便接收不到事件了。IE的event的cancelBubble属性,为true时会阻止事件向上Bubble。jQuery中的 event.stopPropagation()方法对IE浏览器就是通过设置event的cancelBubble属性来实现阻止事件的传播的。但是两 者从语义上来说是有区别的,stopPropagation()会阻止任意阶段的事件传播,而cancelBubble只阻止Bubble阶段的事件传 播。但由于IE的限制,jQuery之类的高层类库只使用Bubble方式的事件传播,这样stopPropagation()就是 cancelBubble(因为没有Capture),两者的功能就是一样的了。 

说了这么多,想必读者应该清楚Capture和Bubble这两个事件传播阶段了吧,至于什么时候该用Capture,什么时候该用 Bubble,还请读者自己裁决。大致的原则,如果你想事件尽快地执行,那就使用Capture,否则便使用Bubble。但是,不要忘记了一个前 提,IE只支持Bubble传播方式,如果你的应用需要支持IE,那么事情就简单多了,那就是永远将useCapture设为false。



以下加速页面加载速度 Google的建议:

1
2
3
4
5
6
7
8
9
10
11
12
<script type= "text/javascript" >
function  downloadJSAtOnload() {
var  element = document.createElement( "script" );
element.src =  "defer.js" ;
document.body.appendChild(element);
}
if  (window.addEventListener)
window.addEventListener( "load" , downloadJSAtOnload,  false );
else  if  (window.attachEvent)
window.attachEvent( "onload" , downloadJSAtOnload);
else  window.onload = downloadJSAtOnload;
</script>

这段代码的意思是等待页面加载完成后,然后再加载外部的“defer.js”文件。下面是测试结果。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值