JavaScript DOM事件模型:捕获阶段与IE绑定陷阱

作者:流萤

时间:2026-10-25

浏览:202

本文深入解析W3C DOM Level 2事件模型的捕获与冒泡阶段,指出Opera浏览器正确实现了规范中目标元素不触发捕获事件的特性,而Firefox和Safari存在偏差。同时对比IE的Microsoft Model,重点分析attachEvent导致的重复绑定及this指向window的问题,建议谨慎使用事件捕获。

文章目录
事件捕捉(Event Capture)的实现问题

W3C DOM事件捕获机制与浏览器差异

在W3C DOM Level 2事件模型规范中,事件在DOM树中的传播过程(从根节点到目标节点)被明确划分为两个阶段:捕获(Capture)和冒泡(Bubbling)。


W3C DOM事件捕获机制与浏览器差异

(from W3C)



若要在支持W3C事件模型的浏览器中创建捕获事件,只需将addEventListener的第三个参数设置为true即可。例如:


代码如下:

 
document.getElementById('foo').addEventListener('click',function(){alert('Hello, world!');},true);

前阵子为了深入理解事件捕获机制,我在Firefox 2、Windows平台下的Safari 3以及Opera 9上进行了实验(由于IE不支持事件捕获,故未包含在内)。实验原理如下图所示:


W3C DOM事件捕获机制与浏览器差异

ID为div1和div2的两个元素均绑定了捕获阶段的事件处理函数,具体设置如下:



当点击#div1(蓝色区域)时,预期会弹出提示框显示“div1”


当点击#div2(黄色区域)时,预期会先弹出“div1”,随后弹出“div2”。这是因为在事件捕获阶段,事件是从根元素向下传播的,#div1作为#div2的父元素,其绑定的click事件自然会在#div2的事件之前执行。


然而,上述预期仅在Firefox 2和Windows平台下的Safari 3中成立。在Opera 9中,实际行为却有所不同:



当点击#div1(蓝色区域)时,没有任何反应


当点击#div2(黄色区域)时,会弹出“div1”,随后不再有任何反应


由此可见,在Opera 9中,目标元素(TargetElement)的click事件并未被执行。经Realazy(orz…)指点,查阅了《Event capture explained》一文,发现Opera的实现才是符合标准的。文中提到:



The DOM spec states that capturing events should not fire on target, because the idea of a capturing event is to detect events before they reach their targets. Because of bugs in Gecko and Safari, web content that is tested mostly with Firefox or other Gecko-based browsers sometimes expects capturing listeners to fire on target. Such content will fail in Opera 7, 8 and current releases of 9 because of its correct implementation of the standard.


大意是:DOM规范明确指出,捕获型事件不应在目标元素上触发,因为捕获事件的初衷是在事件到达目标之前进行监测。Firefox和Safari的实现均存在bug。



再参考W3C DOM Events规范中的原文:



A capturing EventListener will not be triggered by events dispatched directly to the EventTarget upon which it is registered.


因此,整个事件传播的执行顺序应为:



父元素中所有的捕获型事件(如果有)自上而下执行


目标元素的冒泡型事件(如果有)


父元素中所有的冒泡型事件(如果有)自下而上执行


了解这些后,或许暂时避免使用事件捕获更为稳妥。


IE的高级事件处理模型的问题

IE attachEvent的重复绑定与this指向陷阱

重复绑定


IE没有addEventListener,但提供了自己的attachEvent,即Microsoft Model。两者实现基本一致,区别在于attachEvent的第一个参数(事件类型)需加“on”前缀,而addEventListener不需要;此外,由于attachEvent不支持事件捕获,因此没有第三个参数。



然而,attachEvent还有一个致命问题:重复绑定事件。(这一知识点来自ppk on JavaScript)



一个例子:


代码如下:

 
function sayHello(){
alert('Hello, world!');
} 
// W3C Model
$('div1').addEventListener('click', sayHello, false);
$('div1').addEventListener('click', sayHello, false);
// Microsoft Model
$('div1').attachEvent('onclick', sayHello);
$('div1').attachEvent('onclick', sayHello);

在W3C模型中,相同事件处理函数的绑定会被忽略,即第二个$('div1').addEventListener('click', sayHello, false);会被忽略。



而在Microsoft模型中,第二个$('div1').attachEvent('onclick', sayHello);同样会被执行,因此点击#div1时,提示框会弹出两次。更糟糕的是,在detachEvent时,也需要调用两次detachEvent才能彻底从#div1的click事件中移除sayHello。



为什么不继续使用alertID()了?


这是因为IE事件模型的另一个缺陷。在alertID中,使用了this关键字来指代绑定了该事件处理函数的元素。在W3C模型中,alertID中的this指代#div1或#div2。



但在Microsoft模型中,由于缺乏对this的支持,this.id变为undefined,因为此时this指代的是window对象。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章