)时触发一个事件处理程序.除了在所有的段落标签(
tag)注册一个onmouseover事件处理程序之外,取而代之的办法是为文档对象(Document object)注册一个单独的事件处理程序,然后,或者在捕获阶段,或者在冒泡阶段,处理这些事件.
事件传播还有一个很重要的细节.在DOM0模型中,你只能为一个特定的对象的一个特定类型的事件注册一个处理程序.而在DOM2模型中,你可以为一个特定对象的一个特定类型事件注册任意数量的事件处理程序.这也适用于事件传播时,在捕获阶段或者冒泡阶段,事件对象的祖辈的处理函数被调用的情况.
17.2.2. 事件处理程序的注册(Event Handler Registration)
在DOM0的API中,你通过在HTML中设置属性(attribute)或者在JavaScript中设置一个对象的属性(property)的方法注册事件.而在DOM2模型中,你通过调用那个对象的addEventListener( )方法注册事件处理程序.(DOM标准在这个API里使用了术语listener,但是在本文中,我将继续使用这个术语的同义词:handler.)这个方法有三个参数.第一个是被注册的事件类型.事件类型是一个字符串,包含小写的,去掉开头的"on"的HTML中的事件属性名.如果你在DOM0里使用HTML属性onmousedown,你在DOM2中就是使用字符串"mousedown".
第二个参数是指定类型的事件触发的时候应该调用的监听函数.在你的函数被调用的时候,传入了唯一的一个参数:Event对象.这个对象包含了事件的细节信息(如:哪个鼠标键被按下)和一些方法,如:stopPropagation( ).在本章后面将深入讨论事件接口和它的子接口.
addEventListener( )函数的最后一个参数是一个布尔值.如果为true,指定的事件处理程序在事件传播的捕获阶段将捕获事件.如果是false,事件处理程序就是一个正常的事件处理程序了,只有在事件直接发生在该对象上或者发生在子代对象上向上冒泡到达这个元素时,处理程序才被调用.
举个例子,你可以像下面这样使用addEventListener( )方法给一个form元素注册一个提交事件(submit):
document.myform.addEventListener("submit",
function(e) {return validate(e.target); }
false);
如果你想捕获发生在一个特定名字的div中的鼠标按下(mousedown)事件,你可以这样使用addEventListener( ):
var mydiv = document.getElementById("mydiv");
mydiv.addEventListener("mousedown", handleMouseDown, true);
注意,这些例子假设你已经在你的JavaScript代码中定义了函数名为validate( )和handleMouseDown( )的函数.
用addEventListener( )函数注册的事件监听程序运行在它们被定义的作用域.它们并不是在参数的作用域链中被调用的.
因为在DOM2中通过调用一个方法来给对象添加事件监听器,而不是通过设置HTML属性或者JavaScript属性的方法,所以,你可以给一个指定对象的一个特定事件注册多于一个的事件监听程序.如果你通过调用addEventListener( )函数为同一对象的同一事件注册多个监听程序,当那个对象上那个类型的事件发生的时候(或者是向上冒泡,或者是捕获的),所有的处理程序都被调用.重点理解一下:DOM标准并没有保证一个对象的所有监听函数被调用的顺序,因此,你不应该依赖于函数按照被注册的顺序被执行(事实上是根本不按顺序执行).还要注意的是,如果你多次注册相同的监听程序给同一个元素,只有第一次注册的有效,其余的被忽略.
为什么你想要在同一个对象的同一个事件上注册多个事件监听程序呢?因为这有助于将你的软件模块化.假设,你写了一个可重用的JavaScript代码模块,它使用图像上的mouseover事件执行图片轮换.现在再假设你有另一个模块也想使用mouseover事件来显示一些在HTML弹出窗体或者工具提示(Tool tip)上的附加信息.在DOM0的API中,你不得不把这两段代码合并成一个,这样才能共用一个图片对象的onmouseover属性.另一方面,在DOM2的API中,每一个模块都可以注册它需要的事件监听程序,而不必管其它的模块.
removeEventListener( )和addEventListener( )是一对方法,它需要与addEventListener( )同样的三个参数,但它的功能是从一个对象删除一个事件监听函数,而不是添加.它常用于临时注册一个事件监听函数,然后很快就删除这个函数.例如,你得到一个mousedown事件的时候,想为mousemove和mouseup事件注册临时的捕获事件监听函数,这样就可以知道,是否用户拖拽了鼠标.然后在mouseup事件发生的时候,解除这个注册的监听程序.在这种情况下,事件监听器的删除代码如下:
document.removeEventListener("mousemove", handleMouseMove, true);
document.removeEventListener("mouseup", handleMouseUp, true);
addEventListener( )方法和removeEventListener( )方法都被定义在事件目标接口中(the EventTarget interface),在支持DOM2事件模型的Web浏览器中,元素和文档节点实现了这个接口,并且提供了这些事件注册方法.
[*] 从技术上来讲,DOM指出在文档(document)中的所有节点(包括文本节点:Text nodes)都实现了这个事件对象接口.然后事实上,web浏览器仅在元素(Element)和文档节点(Document nodes)上支持事件监听器的注册,还有窗口(Window)对象,尽管这已经超出了DOM的范围.
17.2.3. addEventListener( )和this关键字(addEventListener( ) and the this Keyword)
在原来的DOM0级事件模型中,当一个函数被注册给一个文档元素的某个事件监听程序时,它变成了那个文档元素的一个方法.当这个事件监听程序被调用时,它作为这个元素的一个方法被调用,在函数的内部,this关键字引用当前发生事件的元素.
DOM2是用一种与语言无关的方法写的,它指出监听器(listeners)是对象,而不是简单的函数.绑定了DOM的JavaScript用Javascript函数事件监听器取代对JavaScript对象使用的需求.( The JavaScript binding of the DOM makes JavaScript functions event handlers instead of requiring the use of a JavaScript object.)不幸的是,这个绑定关系并没有实际的指出监听函数如何被调用,也没有指出this关键字的值.
且不去考虑标准的不足,所有已知的实现都调用用addEventListener( )方法注册的处理程序,就像这些处理程序是目标对象的方法一样.也就是说,当监听程序被调用的时候,this关键字引用这个监听程序被注册的那个对象.如果你宁愿不依赖这种未指定的行为,你可以使用传入监听程序的事件对象(Event object)的currentTarget属性.在本章稍候的讨论中你会看到,currentTarget属性引用事件监听程序被注册的对象.
17.2.4. 把对象(Objects)注册为事件监听器(Registering Objects as Event Handlers)
addEventListener( )允许你注册一个事件监听函数.对于面向对象编程,你可能更喜欢定义一个客户端对象的方法作为事件监听程序,然后把它们作为那个对象的方法进行调用.对于Java程序员,DOM标准允许这样做:事件监听程序可以是实现了EvnentListener接口并且有一个名为handleEvent()的方法的对象.在Java中,当你注册一个事件监听程序时,你给addEventListener( )传入一个对象,而不是一个函数.简单的说,绑定了DOM API的JavaScript不需要你去实现EventListener接口,相反的,允许你直接给addEventListener( )传递一个函数引用.
然而,如果你在写一个面向对象的JavaScript程序,并且更喜欢用对象作为事件监听程序,你可以用一个像下边这样的函数来注册:
function registerObjectEventHandler(element, eventtype, listener, captures) {
element.addEventListener(eventtype,
function(event) { listener.handleEvent(event); }
captures);
}
只要一个对象定义了handleEvent( )方法,就可以用这个函数把该对象注册为一个事件监听程序.那个方法作为监听对象的方法被调用,this关键字引用这个监听对象,而不是产生事件的文档元素.
尽管这不是DOM标准的一部分,Firefox(和其它基于Mozilla codebase的浏览器)允许把定义了handleEvent()方法的事件监听对象直接传递给addEventListener()方法,来代替函数.对于这些浏览器,就没有必要定义一个像刚才展示的注册函数了.
17.2.5. 事件模型和事件类型(Event Modules and Event Types)
如我前面所说,DOM2是模块化的,所以,一个实现可以支持其中的一部分而忽略其它对其它部分的支持.事件API(Events API)就是这样一个模块.你可以像这样来测试一个浏览器是否支持这个模块:
document.implementation.hasFeature("Events", "2.0")
然而,事件模块只包含用于基本事件监听结构的API.子模块提供对特定类型事件的支持.每个子模块都提供对一类相关事件类型的支持,并且定义了传入事件监听程序的事件类型.例如,名为MouseEvents的子模块提供了mousedown, mouseup, click等相关事件的类型.它也定义了MouseEvent接口.实现了那个接口的对象,为任何一个被这个模块支持的事件类型,被传入事件监听程序.
表17-2列出了每一个事件模块,它定义的接口,和被它支持的事件类型.注意,DOM2并没有把任何键盘事件标准化,因此这里没有列出键盘事件模块.然而,当前的浏览器都支持键盘事件,在本章的后面,你会了解的更多一些.(此处省略几句和MutationEvents模块相关的描述)
Table 17-2. Event modules, interfaces, and types
Module name Event interface Event types
HTMLEvents Event abort, blur, change, error, focus, load, reset, resize, scroll, select, submit, unload
MouseEvents MouseEvent click, mousedown, mousemove, mouseout, mouseover, mouseup
UIEvents UIEvent DOMActivate, DOMFocusIn, DOMFocusOut
如你在表17-2中所见,HTMLEvents和MouseEvents模块定义的事件类型和DOM0的事件模块是非常相似的.UIEvents模块定义了事件类型,这和被HTML表单元素支持的focus,blur和click事件很相似,但更通用的,所以,他们能被任何可以接受焦点或者被激活的文档元素产生.
如前所述,当一个事件发生的时候,它的监听程序被传入一个实现了那个类型事件的事件接口对象.这个对象的属性提供了对事件监听程序可能有用的细节信息.表17-3再一次列出标准的事件,但这次是按事件类型组织的,而不是事件模型.对于每个事件类型,该表都指出传入它的监听程序的事件对象的种类,是否这个事件有一个可以用preventDefault()方法阻止发生的默认行为.对于HTMLEvents模块中的事件,表格中的第五列指出哪些HTML元素可以产生该事件.对于所有其它的事件类型,第五列指出事件对象的哪些属性包含了有意义的事件细节信息.注意,在这一列中列出的属性,不包括被基本事件接口定义的对所有事件类型都有意义的属性.
Table 17-3. Event types
Event type Interface B C Supported by/detail properties
abort Event yes no ,