`
hax
  • 浏览: 964911 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

    博客分类:
  • AJAX
阅读更多
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规范的浏览器,就不会执行对应的捕获事件监听器。

<div id="div1">
As DOM spec, <strong>Only this area</strong> will trigger
the capturing event listener for click event on the containing
div element.
</div>
<script>
var div1 = document.getElementById('div1');
div1.addEventListener('click', function () { alert('ok'); }, true);
</script>


又如,如果同一个监听器,在同一个元素上注册两次,一次useCapture为true,一次为false,那么在严格遵循DOM规范的浏览器中,监听器在整个event flow中只会被调用一次;反之则会被连续调用两次,而且函数自身是无法判断到底是作为捕获事件监听器被调用,还是作为非捕获事件监听器被调用。当然,实际上是先调用捕获事件监听器再调用非捕获事件监听器的,但是如果加上target对象上的event handler(即onclick之类的事件属性),就又产生了微妙的顺序问题。Gecko的顺序是先执行handler再执行监听器,而WebKit的顺序是先执行捕获事件监听器,再执行handler,最后执行非捕获事件监听器。

Workaround

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

node.addEventListener(type, listener, true);

function listener(evt) {
  if (evt.currentTarget == evt.target) return;
  ...
}


这样就确保了不会在target阶段执行捕获事件监听器。我们也可以判断 evt.eventPhase != evt.CAPTURING_PHASE,但是浏览器的eventPhase也可能有bug,所以最好直接判断currentTarget是否等于target。

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

node.addEventListener(type, listener1, true);
node.addEventListener(type, listener2, false);

function listener1(evt) {
  if (evt.currentTarget == evt.target) return;
  ...
}
function listener2(evt) {
  if (evt.currentTarget != evt.target) return;
  ...
}


或者

node.addEventListener(type, listener, true);
node.parentNode.addEventListener(type, listener, true);

function listener(evt) {
  if (evt.currentTarget == node.parentNode && evt.target != node
  || evt.currentTarget == evt.target) return;
  ...
}


在执行顺序上,前者类似Gecko的行为,后者类似WebKit的行为。

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

总的来说,我建议应尽可能避免使用useCapture=true,因为绝大多数需求都应在target和bubbling阶段处理,特别是涉及UI的事件。如果确实有必要使用捕获事件处理器,应优先考虑符合当前DOM规范的约束,即不在target阶段执行它。这意味着,useCapture应该用于拦截符合条件的子节点事件(许多事件常常仅限于元素),而不是用于一般的事件响应。
5
0
分享到:
评论
2 楼 hax 2008-02-19  
to Lunatic Sun:
我其实更好奇你有什么特别需求一定要使用capture event listener。
1 楼 Lunatic Sun 2008-02-17  
我感觉仅仅需要在capture过程中触发,而不需要在target过程中触发的事件是稀少的,当我在某个元素上添加事件响应时,我一般都希望能够在该元素上发生事件是响应。

相关推荐

    js事件监听机制(事件捕获)总结

    默认情况下,事件处理是采用冒泡的方式进行,除非在添加事件监听器时明确指定使用捕获阶段。 在IE浏览器中,没有标准的捕获事件,只有冒泡事件处理。对于其他大多数现代浏览器,它们支持W3C标准,允许开发者在捕获...

    深入浅出讲解flex中的事件机制

    由于我们的事件监听器设置了`useCapture=true`,所以`onMouseClickHandler`会在`Button1`之前被调用。 3. **冒泡阶段** - 在冒泡阶段,事件会从`Button1`开始向上冒泡,依次经过`Canvas`和`Stage`。但是因为我们...

    【JavaScript源代码】javascript事件冒泡,事件捕获和事件委托详解.docx

    `addEventListener`方法的第三个参数`useCapture`用于启用事件捕获,值为`true`表示在捕获阶段处理事件。尽管在实际应用中事件捕获使用较少,但它有助于在事件到达目标元素之前进行预处理。 下面的代码展示了事件...

    简析JavaScript事件、以及捕获和冒泡

    3. DOM2级事件处理:使用`addEventListener`和`removeEventListener`方法,这提供了更多的灵活性和控制,例如支持多个事件监听器和区分捕获与冒泡。 事件冒泡是JavaScript事件处理的典型模式,通常更受开发者欢迎,...

    详解addEventListener的三个参数之useCapture

    这意味着,如果一个元素和它的子元素都绑定了同一个事件的监听器,并且子元素的监听器设置为`useCapture: true`,那么父元素的监听器将在子元素之前执行。反之,如果都设置为`false`,则子元素的监听器会先执行。 ...

    AS事件侦听案例,源码

    2. **移除事件监听器**:为避免内存泄漏,当不再需要监听事件时,应使用`removeEventListener`移除监听器: ```actionscript button.removeEventListener(MouseEvent.CLICK, handleClick); ``` 3. **事件对象**...

    事件绑定、事件监听、事件委托.pdf

    `addEventListener()`方法具有一个可选的`useCapture`参数,当设置为`true`时,事件处理器会在事件捕获阶段执行;设置为`false`时,在事件冒泡阶段执行,默认值为`false`。 需要注意的是,`addEventListener()`方法...

    【JavaScript源代码】JavaScript中事件冒泡机制示例详析.docx

    为了移除已经添加的事件监听器,可以使用`removeEventListener`方法,提供相同的事件名和处理函数作为参数。 对于不支持`addEventListener`和`removeEventListener`的旧版IE和Opera浏览器,可以使用`attachEvent`和...

    关于flex事件的讲解

    6. **事件捕获**:虽然在Flex中使用捕获阶段相对较少,但可以通过在`addEventListener`方法中设置`useCapture`参数为`true`来注册捕获阶段的监听器。 7. **事件代理**:事件代理是一种优化技术,用于减少事件监听器...

    js之事件冒泡和事件捕获详细介绍

    在JavaScript中,可以通过addEventListener方法添加事件监听器,并且可以通过useCapture参数指定是使用捕获还是冒泡模式。如果useCapture设置为true,则事件在捕获阶段触发;如果设置为false,则事件在冒泡阶段触发...

    事件冒泡解决方案

    我们可以在添加事件监听器时指定`useCapture`参数为`true`,这样事件会在捕获阶段处理,而不是冒泡阶段。但这个方法通常不是用来阻止冒泡,而是为了提前处理事件。 3. **停止事件的默认行为**: 使用`event....

    addeventlistener监听scroll跟touch(实例讲解)

    在Web开发中,尤其是针对移动设备的优化,`addEventListener`是一个关键的方法,它允许我们对DOM元素添加事件监听器。本文将深入探讨如何使用`addEventListener`来监听`scroll`和`touch`事件,并理解其中涉及的技术...

    addEventListener()第三个参数useCapture (Boolean)详细解析

    - 如果 `useCapture` 为 `true` 的事件监听器在捕获阶段执行,而 `useCapture` 为 `false` 的事件监听器在冒泡阶段执行。 - 同一阶段内,事件处理程序的执行顺序取决于它们在DOM中的位置,外部元素的事件处理程序先...

    javascript事件模型实例分析

    通过上述事件模型、事件对象、事件监听器和事件传递的定义与使用技巧的介绍,可以看到JavaScript在处理用户交互方面提供了强大的功能和灵活的机制。了解和掌握这些知识点对于前端开发人员至关重要,它们帮助开发者...

    javascript事件冒泡和事件捕获详解

    通过把事件监听器绑定到一个父元素上,而不是具体的子元素上,可以利用事件冒泡来管理多个子元素的事件。比如在一个颜色列表中,如果绑定了一个点击事件处理函数在父元素ul上,当点击任何一个子元素li时,事件会冒泡...

    js事件机制----捕获与冒泡机制实例分析

    **事件代理**:在实际开发中,事件代理是一种常见的优化策略,它可以减少事件监听器的数量,提高性能。事件代理通常在冒泡阶段使用,因为大多数浏览器对冒泡阶段的事件处理有更好的支持。即使目标元素是动态添加的,...

    DHTML 事件

    2. **事件监听**:使用`addEventListener`方法可以为一个元素添加事件监听器。例如,我们可以监听按钮的点击事件: ```javascript var button = document.getElementById('myButton'); button.addEventListener('...

    原生js事件的添加和删除的封装

    如果设置为true,则意味着事件监听器在捕获阶段执行。 在实际应用中,根据具体需求来选择使用捕获还是冒泡。例如,在处理某些复杂的交互时,可能需要先进行捕获阶段的处理,再进行冒泡阶段的处理,以确保事件处理的...

    带你快速理解javascript中的事件模型

    其中`useCapture`参数决定了事件监听器是在捕获阶段(`true`)还是冒泡阶段(`false`)被触发。 事件捕获和事件冒泡是DOM2级事件模型中的核心概念。事件捕获是指从文档的根节点开始,逐步向下传递至目标节点的过程;...

Global site tag (gtag.js) - Google Analytics