使用捕获事件监听器(useCapture=true)的陷阱及其对策

原创 2008年02月18日 01:44:00
本文原发于我在JavaEye的blog上


DOM event flow有三个phase,capture、target和bubble。通常我们只在后两个阶段处理事件,也即在调用addEventListener (type, listener, useCapture)时,useCapture设为false。偶尔可能会使用所谓捕获事件监听器(Capturing Event Listeners),即useCapture设为true。但有一个很搞的问题,那就是在event.currentTarget等于 event.target的时候(即event flow处于target phase时),是否会调用添加到currentTarget上的useCapture为true的listener?

不同浏览器在这一点上存在分歧。各版本的Firefox和最新版本的Safari会调用,而Opera和老版本的Safari就不会调用。

有人认为DOM Level 2事件规范在这一点上存有歧义,但如果仔细分析,可以确定DOM 2规范的意思确实是不应在目标阶段(target phase)调用捕获事件监听器,W3C发布的测试套件(test suite)也测试了这一点,DOM 3事件规范的草案也再次明确了这一点。

然而基于种种原因,所有版本的Gecko引擎(Firefox等浏览器)和最近版本的WebKit引擎(Safari等浏览器)都会在目标阶段调用包括捕获事件监听器在内的所有监听器,并且有些人认为应该据此修改DOM规范。这种看法确实也存在一定的合理性。

具体情况可参考:
https://bugzilla.mozilla.org/show_bug.cgi?id=235441
http://bugs.webkit.org/show_bug.cgi?id=9127

总之,这一问题在短期内可能不会有确定的结果。无论如何,在使用捕获处理器时应注意避免这一不确定行为的影响。

Gotchas

元素所对应的区域如果有一部分不在任何子元素内(如文本节点),严格遵循DOM规范的浏览器,就不会执行对应的捕获事件监听器。

Html代码 
  1. <div id="div1">  
  2. As DOM spec, <strong>Only this area</strong> will trigger  
  3. the capturing event listener for click event on the containing  
  4. div element.  
  5. </div>  
  6. <script>  
  7. var div1 = document.getElementById('div1');  
  8. div1.addEventListener('click', function () { alert('ok'); }, true);  
  9. </script>  
又如,如果同一个监听器,在同一个元素上注册两次,一次useCapture为true,一次为false,那么在严格遵循DOM规范的浏览器 中,监听器在整个event flow中只会被调用一次;反之则会被连续调用两次,而且函数自身是无法判断到底是作为捕获事件监听器被调用,还是作为非捕获事件监听器被调用。当然,实 际上是先调用捕获事件监听器再调用非捕获事件监听器的,但是如果加上target对象上的event handler(即onclick之类的事件属性),就又产生了微妙的顺序问题。Gecko的顺序是先执行handler再执行监听器,而WebKit的 顺序是先执行捕获事件监听器,再执行handler,最后执行非捕获事件监听器。

Workaround

为了解决这类不确定性,可以采用一个通用的模式如下:

Javascript代码 
  1. node.addEventListener(type, listener, true);  
  2.   
  3. function listener(evt) {  
  4.   if (evt.currentTarget == evt.target) return;  
  5.   ...  
  6. }  
这样就确保了不会在target阶段执行捕获事件监听器。我们也可以判断 evt.eventPhase != evt.CAPTURING_PHASE,但是浏览器的eventPhase也可能有bug,所以最好直接判断currentTarget是否等于 target。

此外,有些时候我们反而希望确保在target阶段执行(这也正是认为应该修改DOM规范的理由之一)。可以采用以下模式:

Javascript代码 
  1. node.addEventListener(type, listener1, true);  
  2. node.addEventListener(type, listener2, false);  
  3.   
  4. function listener1(evt) {  
  5.   if (evt.currentTarget == evt.target) return;  
  6.   ...  
  7. }  
  8. function listener2(evt) {  
  9.   if (evt.currentTarget != evt.target) return;  
  10.   ...  
  11. }  
或者

Javascript代码 
  1. node.addEventListener(type, listener, true);  
  2. node.parentNode.addEventListener(type, listener, true);  
  3.   
  4. function listener(evt) {  
  5.   if (evt.currentTarget == node.parentNode && evt.target != node  
  6.   || evt.currentTarget == evt.target) return;  
  7.   ...  
  8. }  
在执行顺序上,前者类似Gecko的行为,后者类似WebKit的行为。

而且最好不要使用后者,因为它强制要求事件监听器引用target节点,从而构成了闭包,这降低了listener的可重用性。

总的来说,我建议应尽可能避免使用useCapture=true,因为绝大多数需求都应在target和bubbling阶段处理,特别是涉及 UI的事件。如果确实有必要使用捕获事件处理器,应优先考虑符合当前DOM规范的约束,即不在target阶段执行它。这意味着,useCapture应 该用于拦截符合条件的子节点事件(许多事件常常仅限于元素),而不是用于一般的事件响应。

使用捕获事件监听器(useCapture=true)的陷阱及其对策

 DOM event flow有三个phase,capture、target和bubble。通常我们只在后两个阶段处理事件,也即在调用addEventListener (type, listener,...
  • wishfly
  • wishfly
  • 2008年02月20日 16:45
  • 543

useCapture:true与flase的表现区别 事件的冒泡与捕获

Document 今天看了一下事件绑定,写篇博客记忆一下!
  • u011456552
  • u011456552
  • 2016年09月21日 17:41
  • 1579

AS3 event flow 事件冒泡机制 以及 stopImmediatePropagation() stopPropagation()用法 及useCapture的用法

如果一个mc下有一个btn,2者都添加click事件:捕获阶段:鼠标在btn上发出点击事件,首先捕捉到该事件的事stage.,然后事件往下传递到mc,再到btn..(如果鼠标事件发生在btn按钮中的一...
  • kingarthas37
  • kingarthas37
  • 2010年11月01日 16:40
  • 846

Jquery事件监听

Jquery事件监听   $(selector).bind(event,data,function,map);   event 必须 ,规定添加到元素的一个或多个事件,由空格分隔。 如click ...
  • weixin_41235247
  • weixin_41235247
  • 2017年12月04日 16:11
  • 41

addEventListener(type, fn , true/false)绑定事件第三个参数作用以及利用事件的冒泡,实现事件委托

addEventListener( type , fn , true/false )
  • qq_34626003
  • qq_34626003
  • 2017年05月02日 11:26
  • 1065

Java事件及其监听器小结

最近在用NetBeans IDE 写Java界面程序,总结一下控件的事件及其监听器。 事件 事件方法 监听器 监听器方法 ...
  • y5492853
  • y5492853
  • 2017年03月03日 18:41
  • 283

JAVA编程一些必须注意小心的陷阱

《Java Puzzlers》(中文译名《Java解惑》),一句话,好书,有趣,短小精悍的行文,主要是介绍JAVA编程一些必须注意小心的陷阱(trap)。第一章:表达式之谜,俺只记下一些有趣的例子,很...
  • thinkpadleo
  • thinkpadleo
  • 2006年05月04日 09:36
  • 886

Android--UI事件监听器及其处理程序

setOnxxxxListener            new 类型.OnxxxxListener  Android的UI设计中,除了产生基本的UI,更重要的是UI的事件处理(UI Event...
  • perfect2011
  • perfect2011
  • 2011年10月31日 09:46
  • 890

原因分析和对策表要素

 
  • lzmtw
  • lzmtw
  • 2008年03月26日 17:11
  • 2373

useCapture

可以设置useCapture参数,以确定侦听器是运行于捕获阶段、目标阶段还是冒泡阶段。 (1)如果将useCapture设置为true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段处理事件。 (...
  • cruelchen
  • cruelchen
  • 2013年07月12日 10:58
  • 382
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用捕获事件监听器(useCapture=true)的陷阱及其对策
举报原因:
原因补充:

(最多只允许输入30个字)