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应该用于
拦截符合条件的子节点事件(许多事件常常仅限于元素),而不是用于一般的事件响应。
分享到:
相关推荐
默认情况下,事件处理是采用冒泡的方式进行,除非在添加事件监听器时明确指定使用捕获阶段。 在IE浏览器中,没有标准的捕获事件,只有冒泡事件处理。对于其他大多数现代浏览器,它们支持W3C标准,允许开发者在捕获...
由于我们的事件监听器设置了`useCapture=true`,所以`onMouseClickHandler`会在`Button1`之前被调用。 3. **冒泡阶段** - 在冒泡阶段,事件会从`Button1`开始向上冒泡,依次经过`Canvas`和`Stage`。但是因为我们...
`addEventListener`方法的第三个参数`useCapture`用于启用事件捕获,值为`true`表示在捕获阶段处理事件。尽管在实际应用中事件捕获使用较少,但它有助于在事件到达目标元素之前进行预处理。 下面的代码展示了事件...
3. DOM2级事件处理:使用`addEventListener`和`removeEventListener`方法,这提供了更多的灵活性和控制,例如支持多个事件监听器和区分捕获与冒泡。 事件冒泡是JavaScript事件处理的典型模式,通常更受开发者欢迎,...
这意味着,如果一个元素和它的子元素都绑定了同一个事件的监听器,并且子元素的监听器设置为`useCapture: true`,那么父元素的监听器将在子元素之前执行。反之,如果都设置为`false`,则子元素的监听器会先执行。 ...
2. **移除事件监听器**:为避免内存泄漏,当不再需要监听事件时,应使用`removeEventListener`移除监听器: ```actionscript button.removeEventListener(MouseEvent.CLICK, handleClick); ``` 3. **事件对象**...
`addEventListener()`方法具有一个可选的`useCapture`参数,当设置为`true`时,事件处理器会在事件捕获阶段执行;设置为`false`时,在事件冒泡阶段执行,默认值为`false`。 需要注意的是,`addEventListener()`方法...
为了移除已经添加的事件监听器,可以使用`removeEventListener`方法,提供相同的事件名和处理函数作为参数。 对于不支持`addEventListener`和`removeEventListener`的旧版IE和Opera浏览器,可以使用`attachEvent`和...
6. **事件捕获**:虽然在Flex中使用捕获阶段相对较少,但可以通过在`addEventListener`方法中设置`useCapture`参数为`true`来注册捕获阶段的监听器。 7. **事件代理**:事件代理是一种优化技术,用于减少事件监听器...
在JavaScript中,可以通过addEventListener方法添加事件监听器,并且可以通过useCapture参数指定是使用捕获还是冒泡模式。如果useCapture设置为true,则事件在捕获阶段触发;如果设置为false,则事件在冒泡阶段触发...
我们可以在添加事件监听器时指定`useCapture`参数为`true`,这样事件会在捕获阶段处理,而不是冒泡阶段。但这个方法通常不是用来阻止冒泡,而是为了提前处理事件。 3. **停止事件的默认行为**: 使用`event....
在Web开发中,尤其是针对移动设备的优化,`addEventListener`是一个关键的方法,它允许我们对DOM元素添加事件监听器。本文将深入探讨如何使用`addEventListener`来监听`scroll`和`touch`事件,并理解其中涉及的技术...
- 如果 `useCapture` 为 `true` 的事件监听器在捕获阶段执行,而 `useCapture` 为 `false` 的事件监听器在冒泡阶段执行。 - 同一阶段内,事件处理程序的执行顺序取决于它们在DOM中的位置,外部元素的事件处理程序先...
通过上述事件模型、事件对象、事件监听器和事件传递的定义与使用技巧的介绍,可以看到JavaScript在处理用户交互方面提供了强大的功能和灵活的机制。了解和掌握这些知识点对于前端开发人员至关重要,它们帮助开发者...
通过把事件监听器绑定到一个父元素上,而不是具体的子元素上,可以利用事件冒泡来管理多个子元素的事件。比如在一个颜色列表中,如果绑定了一个点击事件处理函数在父元素ul上,当点击任何一个子元素li时,事件会冒泡...
**事件代理**:在实际开发中,事件代理是一种常见的优化策略,它可以减少事件监听器的数量,提高性能。事件代理通常在冒泡阶段使用,因为大多数浏览器对冒泡阶段的事件处理有更好的支持。即使目标元素是动态添加的,...
2. **事件监听**:使用`addEventListener`方法可以为一个元素添加事件监听器。例如,我们可以监听按钮的点击事件: ```javascript var button = document.getElementById('myButton'); button.addEventListener('...
如果设置为true,则意味着事件监听器在捕获阶段执行。 在实际应用中,根据具体需求来选择使用捕获还是冒泡。例如,在处理某些复杂的交互时,可能需要先进行捕获阶段的处理,再进行冒泡阶段的处理,以确保事件处理的...
其中`useCapture`参数决定了事件监听器是在捕获阶段(`true`)还是冒泡阶段(`false`)被触发。 事件捕获和事件冒泡是DOM2级事件模型中的核心概念。事件捕获是指从文档的根节点开始,逐步向下传递至目标节点的过程;...