DOM Level 2 中介绍的模块之一是Events模块,它指定一个方法来处理DOM文档中的事件。
早期的版本的浏览器中内置的事件模块使用了事件处理程序,这些处理程序作为属性附在元素之上。当需要动态地依附在元素上和从元素中删除时,使用这个方法就会很不方便。另外,使用这种模块把每个事件处理程序附在非Element节点上是不可能的,这是因为只有Element可以具有属性。
注意:IE有一个类似DOM Level 2 Events接口的事件模型,但不遵循W3C标准。
1、事件流通过文档的方式
术语事件流用于描述事件发生在DOM的特定实现中并且遍历文档的过程。
每个时间都有相关的事件目标。这个目标通常是发生事件所在文档中的一个特定节点。
下图说明目标的事件监听器处理输入事件的方式:
文档元素
输入事件E
事件为正在侦听事件E的文档
元素触发已注册的事件侦听器。
事件的事件侦听器1 事件的事件侦听器3 事件的事件侦听器2
事件在文档树中流动的方向有两个:向上或向下。为给定事件注册的事件监听器的类型规定了事件的流动方向。事件监听器可以指定它自己使用事件捕获,在这种情况下它将是自己所有的派生事件之前的事件接收者。这正是事件可以在文档树中向下流动的方式。一些类型的事件可以触发有关它们目标的监听器,然后向上移动文档树到达每个继承的父目标。这些类型的事件称为冒泡(bubbling)事件。
下图说明了事件在文档树种流动的两种不同方向。
6 5 2 1
B D E C F G 3 4 A
假设用户单击节点D,并且节点A、B和D对这个单击事件都有两个事件监听器,那么一个事件监听器使用了事件捕获(向下)而另一个事件监听器没有使用事件捕获(向上,冒泡)。节点A将接收第一个事件并触发它的基于捕获的事件侦听器(图中点1)。接下来,节点B将触发它的基于捕获的事件侦听器(点2)。最后,节点D将触发它的事件侦听器(点3)。事件将在这个点后退流过文档树,对节点(没有使用事件捕获)的触发事件侦听器——首先是对节点D(点4),然后是对节点B(点5),最后后退到节点A(点6)。
2、EventTarget接口和EventListener接口
DOM Level2 EventTarget接口的IDL定义如下:
interface EventTarget{
void addEventListener(in DOMString type,in EventListener listener,in boolean useCapture);
void removeEventListener(in DOMString type,in EventListener listener,in boolean useCapture);
boolean dispatchEvent(in Event evt) raises(EventException);
};
addEventListener()方法把EventListener接口附在一个特定事件目标(通常是文档节点)之上。type参数可以指定侦听者处理的事件类型(见DOM2 Events模块规范)。listener参数是将要处理事件的特定EventListener接口。useCapture参数可以指定当侦听事件时这个侦听器是否使用事件捕获。如果这是true,那么这个侦听器的事件目标将接受这种类型的事件,并且它将在文档树中的自己的任何派生事件之前注册这种类型的事件。
dispatchEvent()方法允许DOM应用程序手工地把事件分派到事件模型中。这些手工分派的事件将会有和通常由实现生成的事件一样的冒泡类型和捕获行为。
EventListener 接口指定为
interface EventListener{
void handleEvent(in Event evt);
};
handleEvent()方法,它可以把一个事件作为它唯一的参数。
3、事件阶段。
DOM事件可以位于三个阶段中:Event Capture阶段、At Target阶段和Event Capture阶段。
当正在使用事件捕获的EventListener接口处理一个事件时,这个事件处在Event Capture阶段中。每个为事件注册的EventListener接口都将在一个事件目标之前首先处理这个事件,如果EventListener接口正在使用事件捕获(当它附在这个目标上时它的useCapture参数设置为true),那么将注册这个事件目标。
当附在真正事件目标上的EventListener接口处理一个事件时,这个事件在At Target阶段中。
如果指定这个事件为bubbling事件,那么这个事件开始向后流过文档树层次结构直到Document节点本身。
注意:根据DOM规范,当事件开始流动时要在实际分派事件之前确定从初始事件目标到树顶部的事件目标链。如果当事件正在流动时修改了文档树的结构,那么事件将遵循树的初始状态的路径。当然,并不是所有类型的事件都流动(例如:焦点事件)。可以对一些流动事件引用DOM2 Events模块规范。
4、事件接口。
DOM Events规范中的所有事件都把Event接口作为它们的基本接口.
Interface Event{
//Phase Type
const unsigned short CAPTURING_PHASE=1;
const unsigned short AT_TARGET=2;
const unsigned short BUBBLING_PHASE=3;
//type属性可以指定事件的类型(单击和鼠标激活链接等)
readonly attribute DOMString type;
// target属性可以指定这个事件的事件目标——该事件最初发送到的节点。
readonly attribute EventTarget target;
// currentTarget属性是当前正在处理该事件的事件目标。
readonly attribute EventTarget currentTarget;
// eventPhase属性可以指出事件当前所处的阶段(Event Capture阶段、At Target阶段和Event Capture阶段)
readonly attribute unsigned short eventPhase;
readonly attribute boolean bubbles;
//cancelable属性可以指出事件是否可以阻止自己的默认行为。如果为true,那么可以调用preventDefault()方法。
readonly attribute boolean cancelable;
// timeStamp属性以毫秒为单位,指出事件创建的时间。
readonly attribute DOMTimeStamp timeStamp;
// stopPropagation方法可以阻止事件传播到下一个事件目标,并且这对所有事件阶段中的事件都是有效的。
void stopPropagation();
// preventEefault方法将阻止DOM实现采取任何和这个事件类型有关的默认行为。
void preventEefault();
//如果应用程序创建了它自己的事件,那么它可以使用这个方法初始化它们。
void initEvent(in DOMString eventTypeArg,in boolean canBubbleArg,in boolean cancelableArg);
};
5、DOM应用程序处理事件的方式
下面示例使用pre-DOM和基于属性的事件处理程序来处理事件:
<HTML>
<HEAD>
</HEAD>
<BODY>
<A HREF="#" onmouseover="this.style.fontWeight=‘bold‘" onmouseout="this.style.fontWeight=‘normal‘">Link One</A>
<A HREF="#" onmouseover="this.style.fontWeight=‘bold‘" onmouseout="this.style.fontWeight=‘normal‘">Link Two</A>
</BODY>
</HTML>
当用户移动鼠标激活这个文档中的链接时,这个链接变为粗体。为了使用DOM 2事件侦听器完成同样的事情,代码如下:(IE不支持,请使用支持的浏览器,如firefox)
<HTML>
<HEAD>
</HEAD>
<BODY>
<A HREF="#" ID="link1">Link One</A>
<A HREF="#" ID="link2">Link Two</A>
<SCRIPT LANGUAGE="JavaScript">
<!--
function makeBold(evt)
{
evt.currentTarget.style.fontWeight="bold";
}
function makeNormal(evt)
{
evt.currentTarget.style.fontWeight="normal";
}
document.getElementById("link1").addEventListener("mouseover",makeBold,false);
document.getElementById("link1").addEventListener("mouseout",makeNormal,false);
document.getElementById("link2").addEventListener("mouseover",makeBold,false);
document.getElementById("link2").addEventListener("mouseout",makeNormal,false);
//-->
</SCRIPT>
</BODY>
</HTML>
每个事件侦听器函数都可以附加在一个以上的目标上。
处理程序使用evt.currentTarget而不是evt.target的原因是什么?这是因为事件总是把树中最深层嵌套的节点作为目标。在这种情况下,目标是表示文本字符串link1 和link2的文本节点,然而这些并不是您想要的——您想让<A>标记修改它们的样式。事件的目标属性将设置为事件的初始目标,它的anchor标记的文本节点。Mouseover事件是一个bubbling事件,所以正好可以等待它向上流动到<A>标记的事件侦听器,然后再使用currentTarget(这时将设置为<A>标记的节点)对它进行操作。
下面的程序代码示范事件的捕获和冒泡的用法。
<HTML>
<HEAD>
</HEAD>
<BODY>
<UL ID="List1">
<LI>List item 1</LI>
<LI>List item 2</LI>
<LI>List item 3</LI>
<UL ID="List2">
<LI>List item 4</LI>
<LI ID="item5">List item 5</LI>
<LI>List item 6</LI>
<UL ID="List3">
<LI>List item 7</LI>
<LI ID="item8">List item 8</LI>
<LI>List item 9</LI>
</UL>
</UL>
</UL>
<TEXTAREA ID="output" NAME="textfield" ROWS="15" COLS="75"></TEXTAREA>
<A HREF="#" ONCLICK="document.getElementById(‘output‘).value=‘‘;">Clear Entries</A>
<SCRIPT LANGUAGE="JavaScript">
<!--
function clickListener(evt)
{
var outStr;
outStr="Event Target:";
//获得Event Target
if(evt.target.tagName) //target
outStr+=evt.target.tagName+";";
else
outStr+="(text node);";
outStr+="Current Target:";
//获得Current Target
if(evt.currentTarget.tagName)//currentTarget
{
outStr+=evt.currentTarget.tagName+"("+evt.currentTarget.getAttribute("ID")+")"+";";
}
else
outStr+="(text node);";
//获得(text node)
if(evt.eventPhase==1) //Event Capture阶段
outStr+="Captrue phase\n";
else if(evt.eventPhase==2) //At Target阶段
outStr+="At Target phase\n";
else if(evt.eventPhase==3) //Event Capture阶段
outStr+="Bubbling phase\n";
document.getElementById("output").value+=outStr;//把outStr添加到textarea
}
document.getElementById("List1").addEventListener("click",clickListener,true);
document.getElementById("List1").addEventListener("click",clickListener,false);
document.getElementById("List2").addEventListener("click",clickListener,true);
document.getElementById("List2").addEventListener("click",clickListener,false);
document.getElementById("List3").addEventListener("click",clickListener,true);
document.getElementById("List3").addEventListener("click",clickListener,false);
document.getElementById("item5").addEventListener("click",clickListener,true);
document.getElementById("item5").addEventListener("click",clickListener,false);
document.getElementById("item8").addEventListener("click",clickListener,true);
document.getElementById("item8").addEventListener("click",clickListener,false);
//-->
</SCRIPT>
</BODY>
</HTML>
值得注意的是对每个事件目标都有两个对addEventListener方法的调用.这时为了给事件捕获注册事件处理程序以及非捕获(冒泡事件)事件处理程序.
当单击列表项List Item5时,将会出现6个触发的事件.前3个触发的事件将是捕获关于它们子节点的事件的捕获样式事件处理程序,在文档的顶部开始:List1、List2、Item5。下面3个触发事件将是当事件向上流过树时的单击事件:item5、List2、List1。因为事件时对准List Item5(对应的<LI>列表项的子节点)的文本节点,所以这里没有显示的目标阶段。如果单击文本下面的小实心圆,那么事件的目标将是<LI>标记本身,目标阶段会反映出来。
使用捕获样式事件侦听器对节点的子节点组指派相同的行为是一种非常强大的方式。使用这个方法可以使得子节点显示相同的行为,而不必把事件侦听器附在每个事件上。
6、事件侦听器与旧式侦听器进行互操作的方式
在DOM Level 2 Events规范之前是使用关于预期事件目标的属性来注册事件侦听器.因为当前所有的浏览器仍然允许这个方法提供向后兼容.
根据DOM Level 2 Events规范,DOM实现可以查看关于元素的事件处理程序属性的设置,这和创建并注册一个关于该元素的事件侦听器是相似的,同时useCapture参数默认为false.
如下:
someEvent.onmouseover=myEventFunction;
和
someEvent.addEventListener(“mouseover”,myEventFunction,false);
是相同的.
当使用事件处理程序属性时,对事件侦听器(已经通过关于元素的属性注册了)来说没有任何方式可以获得有关事件的上下文信息,例如当前目标阶段或事件阶段.
本文参考:<<文档对象模型>>
联系客服