转自http://blog.csdn.net/wishfly
神奇的DOM
"
DOM Level 2
中介绍的模块之一是
Events
模块,它指定一个方法来处理
DOM
文档中的事件。
早期的版本的浏览器中内置的事件模块使用了事件处理程序,这些处理程序作为属性附在元素之上。当需要
动态地依附在元素上和从元素中删除
时,使用这个方法就会很不方便。另外,使用这种模块把每个事件处理程序附在非
Element
节点上是不可能的,这是因为只有
Element
可以具有属性。
注意:
IE
有一个类似
DOM Level 2 Events
接口的事件模型,但不遵循
W3C
标准。
1
、事件流通过文档的方式
术语事件流用于描述事件发生在
DOM
的特定实现中并且遍历文档的过程。
每个时间都有相关的事件目标。这个目标通常是发生事件所在文档中的一个特定节点。
下图说明目标的事件监听器处理输入事件的方式:
输入事件
E
事件为正在侦听事件
E
的文档
元素触发已注册的事件侦听器。
事件在文档树中流动的方向有两个:向上或向下。为给定事件注册的事件监听器的类型规定了事件的流动方向。事件监听器可以指定它自己使用事件捕获,在这种情况下它将是自己所有的派生事件之前的事件接收者。这正是事件可以在文档树中向下流动的方式。一些类型的事件可以触发有关它们目标的监听器,然后向上移动文档树到达每个继承的父目标。这些类型的事件称为冒泡
(bubbling)
事件。
下图说明了事件在文档树种流动的两种不同方向。
表示每个节点的事件侦听器
单击事件
假设用户单击节点
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
、
List
2、
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);
是相同的.
当使用事件处理程序属性时,对事件侦听器(已经通过关于元素的属性注册了)来说没有任何方式可以获得有关事件的上下文信息,例如当前目标阶段或事件阶段.
"