利用Dojo进行DOM事件编程



Dojo提供了一个DOM事件框架,该框架在所有Dojo支持的浏览器中具有一致的行为。Dojo更是修复了IE免费提供的内存泄漏问题。Dojo的DOM事件框架函数与W3C DOM Level 2的事件模型(http://www.w3.org/TR/DOM-Level-2-Events/)十分相似。对于支持W3C DOM Level 2的浏览器,Dojo只是简单地把事件处理函数的关联/取消关联请求传递给DOM函数addEvent- Listener/removeEventListener。对于与W3C模型有很大差异的浏览器模型,Dojo对W3C模型进行了模拟。最后要提到的是,W3C模型为自定义行为留下了接口,Dojo在其上定义了自己的行为。

下面概述一下事件驱动编程模型。事件发生时,将调用一个函数——称为处理函数(handler)。事件有时被称为信号(signal),可能是一个硬件事件,例如鼠标手势;或是一个软件事件,例如提交表单。处理函数又被称为监听器(listener)或回调函数(callback),是一个拥有明确定义的参数集合并在明确定义的上下文中执行的函数。图6-1是浏览器事件编程工作原理概念图。

图6-1 浏览器中的事件处理

6.1.1 编写事件处理函数

我们通过编写一个简单的用来打印事件信息的事件处理函数,来研究Dojo中的DOM事件框架。然后,利用Dojo把处理函数与一个点击事件关联。在此过程中,我们还将描述事件传播和默认处理过程,两者都会受到事件处理函数的影响。最后还要介绍Dojo为处理键盘事件对W3C模型进行的扩展。

6.1.2 处理函数签名

处理函数也是函数。Dojo事件框架为处理函数提供了单一的参数,称为事件对象。该对象包含一些属性和方法,这些属性和方法按照W3C事件模型的规定描述和控制事件——这对于没有本地实现这一模型的IE也是一样的。如果你要编写一个不需要事件对象的处理函数,只需声明一个不带参数的函数[1]。由于要记录事件对象的信息,因此我们的处理函数要接收事件对象作为唯一的参数:

有三种类型的事件对象构成了一个继承单链:MouseEvent继承自UIEvent,UIEvent又继承自Event。图6-2显示了整个事件类型空间(图中也显示了键盘事件对象,我们随后将对其进行讨论)。图中包含了每类事件对象中可用的只读属性。

传给处理函数的事件对象的实际类型依赖于被处理事件的类型。图6-3中的事件目录列出了每种事件提供的事件对象类型。

由于最终要将处理函数与点击事件关联,因此我们的处理函数接收一个UIEvent类型的事件对象,并把这个对象中包含的事件信息输出到调试控制台:

 

要注意我们的处理函数没有返回值。处理函数都不需要返回值,因为它的任何返回值都将被忽略。

现在,我们已经定义了事件处理函数,下一步是要把它与事件关联。在此之前,我们要先了解Dojo中DOM事件框架的一些细节。

6.1.3 键盘事件对象

Event、UIEvent和MouseEvent类型已被W3C DOM Level 2事件模型标准化了。遗憾的是,W3C没有提到键盘事件。Dojo增强了原有模型,它在事件接口中为上/下/左/右按键事件添加了对应的属性(见图6-2)。

当你为键盘事件编写处理函数时,要注意不同浏览器对同一按键使用了不同的虚拟键码。例如,F10在Firefox和IE中是121,但在Safari中却是63245。Dojo利用dojo.keys解决这个问题,它提供一个从特殊键名到虚拟键码的映射表。例如,我们可以这样写:

 

 

这段代码在所有Dojo支持的浏览器中都能正常工作。下面的特殊按键都是dojo.keys的属性。

q   SHIFT、CTRL、ALT、CAPS_LOCK、NUM_LOCK、SCROLL_LOCK

q   TAB、SPACE

q   ENTER、ESCAPE、PAUSE

q   SELECT、CLEAR

q   PAGE_(UP|DOWN|LEFT|RIGHT)、HOME、END、(LEFT|RIGHT)_ARROW

q   INSERT、BACKSPACE、DELETE

q   NUMPAD_0至NUMPAD_9、NUMPAD_PLUS、NUMPAD_MINUS、NUMPAD_MULTIPLY、NUMPAD_DIVIDE、NUMPAD_ENTER、NUMPAD_PERIOD

q   (LEFT|RIGHT)_WINDOW

q   F1至F15

q   HELP

6.1.4 事件传播

对于有些事件,浏览器会把一个事件分派到多个DOM节点,这一过程称为事件传播。处理函数可以影响事件的传播,理解事件传播对于优化处理函数代码非常重要。

一般点击事件的过程是,当用户点击一个DOM节点时,一个点击事件被发送到目标节点(目标节点就是事件发生的节点)、它的父节点、祖父节点等等,一直到文档树的最上层。这个发送的过程被称为冒泡(bubbling)。无论对错,W3C事件规范中规定,一些事件不能完全地冒泡下去,Dojo也没有改变这种行为。在图6-3中,我们用B来标记可以冒泡的事件。

冒泡使我们可以将代码统一放到父节点。例如,假设我们有一个div节点,它含有100个子节点,每个子节点在结构上都是一致的并都使用同一个点击事件处理函数。我们可以为每一个子节点都关联这个处理函数,也可以只为它们的父节点div关联这个处理函数。父节点所关联的处理函数要检查事件对象的target属性,以此确定实际被点击的子节点。这个解决方法显然更胜一筹。

有时你可能想阻止冒泡行为。例如,树结构中的一个底层节点的处理函数调用了它祖先节点的处理函数来处理一个事件,那么我们肯定不希望祖先节点的处理函数因为冒泡行为而再次被调用。这很容易实现:

 

许多浏览器都支持把事件发送到目标对象前执行捕获(capturing)操作。捕获的工作方式与冒泡恰好相反,它从目标对象的最远祖先开始一直到目标对象的父节点,依次调用它们的事件处理函数。

因此,最长的事件传播路径是下面给出的。

(1) 捕获阶段:从最远祖先开始到父节点为止,调用目标节点祖先的处理函数。

(2) 调用目标节点的处理函数。

(3) 冒泡阶段:从父节点开始到最远祖先为止,调用目标节点祖先的处理函数。

当一个处理函数被关联到一个事件时,它或者被关联到捕获阶段,或者被关联到冒泡阶段。如果你想在两个阶段都看见这个事件发生,就要为两个阶段各关联一个处理函数(尽管两个处理函数可能相同)。

我们可以在事件处理过程中的任何地点利用任何处理函数阻止事件传播,只需调用事件对象的stopPropagation方法。

同样我们也可跳过捕获阶段而不影响后面的两个阶段,这也正是Dojo事件框架的工作方式。这是件非常好的事情。当你思考之后就会发现捕获行为往往是错误的[1]。捕获意味着通用的处理函数在特化的处理函数之前被调用。我们为什么要在DOM树的低层添加处理函数呢,还不就是为了“重载”树中高层的一些处理函数吗?当然,如果有必要的话,低层的处理函数仍然可以直接调用高层的处理函数[2]。这很像在子类的重载函数中调用父类中被重载的函数。

我们要增强点击处理函数,使它在按下shift键时点击不会冒泡:

 

 

随后我们就将这个处理函数与一个小型DOM树中的所有节点关联。那个示例会先显示冒泡行为(当点击时),然后立即阻止冒泡行为(当按下shift键时点击)。

6.1.5 默认处理

无论是否有处理函数与之关联,每一个DOM事件都会引发浏览器执行一些默认处理行为。例如,当用户点击表单上的提交按钮,浏览器就把表单数据发送到服务器。事件处理函数可以撤销某些类型事件的默认处理行为。例如,一个表单提交事件的处理函数在表单中发现错误时,可以通过取消默认处理行为来阻止表单被提交到服务器。

事件处理函数可以通过调用事件对象的preventDefault方法取消默认处理行为,如下面代码所示:

 

 

图6-3显示了可以取消的事件的列表。

最后,介绍一个十分好用的函数dojo.stopEvent(event),它同时调用event.preventDefault()和event.stopPropagation()这两个函数。

 

 

这就是关于编写处理函数的全部内容。接下来,我们解决如何关联处理函数与DOM事件的问题。

6.1.6 关联处理函数

Dojo提供了一个用来关联处理函数与DOM事件的dojo.connect方法(后面我们会介绍dojo.connect的更多功能)。这个函数是Dojo事件框架的关键,因为它使事件处理函数按我们所描述的那样去执行,而且不依赖于任何特定的浏览器。它的签名如下:

 

handle= dojo.connect(obj, event, context, handler)

 

从本质上讲,调用dojo.connect(obj, event, context, handler)关联一个处理函数到一个DOM节点事件,就如同调用W3C DOM的obj.addEventListerner(event, dojo.hitch(context, handler))。而且,即使当前浏览器不直接支持addEventListener的IE版本,Dojo也能做到同样的事情。

这个函数的前两个参数obj(一个DOM节点)和event(一个字符串)定义了要关联的事件。图6-3列出了可用的DOM事件的名称。接下来的两个参数,context(可选,是一个对象)和handler(一个函数或字符串),定义了处理函数。这里context参数是语法糖衣,它使我们通过绑定函数到上下文(例如,绑定一个方法到一个对象)得到一个处理函数时,不需要显式调用dojo.hitch。我们将会看到这一模式在Dojo中被频繁地使用。如果dojo.hitch让你感到一头雾水,请快速复习5.1节。

有了dojo.connect的帮助,我们可以把事件处理函数关联到我们一直在进行的工作上。首先,让我们创建一个实践处理函数用的小文档:

 

我们再把处理函数关联到body子树中的每一个DOM节点:

 

 

由于这个处理函数不需要任何特定上下文,因此忽略上下文参数。

要记住重要的一点,我们是在创建各种事件框架功能的示例,而不是优化一个DOM子树中所有节点点击事件处理函数的代码。例如,我们为了示例冒泡行为而为所有节点关联处理函数,但一个“真实”的程序只需为子树的根节点关联处理函数,因为无论将处理函数关联到子节点还是父节点,它都做相同的事情。我们通过id属性查找节点然后显式地进行关联,随后在7.2节中将看到如何利用dojo.query在一行代码中完成上述工作。

当文档加载完毕,点击第二个列表元素后,控制台将显示下面的消息:

 

 

在第一条消息中eventObj.currentTarget和eventObj.target的值是一样的。随着事件向上传播,eventObj.target保持不变,eventObj.currentTarget的变化反应了处理函数在子树中的位置。点击同一个列表元素并按下Shift键,处理函数冒泡行为被阻止,结果只有一条消息发送到调试控制台:

 

 

利用dojo.connect可以将多个不同的处理函数关联到相同的(node, event)事件。这种情况下,处理函数的调用顺序不确定。这足以说明关联多个处理函数不是一个好想法。如果你确实需要这样做,最好将多个处理函数按照明确的顺序合并到一个新的函数中,如下所示。

 

 

在示例中,我们使用一个函数字面量创建一个处理函数,它以明确的顺序调用其他两个处理函数。同时我们也省略了context参数。要记住,dojo.connect通过调用dojo.hitch (context,handler)来创建处理函数。由于没有提供context参数,示例会像下面代码那样创建处理函数:

 

 

这正是我们想要的效果,当处理函数被调用时,this变量的值没有引用到当前的目标节点。尽管没有标准规定,但当处理函数被调用时,大部分浏览器的事件API都把this引用到当前目标DOM节点。但是,dojo.connect将dojo.hitch(context, handler)的返回值关联到事件,其中hitch只是为了保证调用handler时满足this==context[3]这个条件。

我们的建议是,不要在处理函数代码中假设this已引用到当前目标。否则会产生脆弱的代码并与标准不兼容。相反,应使用event.target或event.currentTarget,从而使其独立于处理函数的语义。

至此,我们已经可以把创建的处理函数与事件关联,但是所有的dojo.connect调用代码应该放在什么地方呢?接下来我们会给出答案。

6.1.7 利用dojo.addOnLoad执行初始化代码

当我们调用dojo.connect把处理函数连接到DOM节点时,最好确保DOM树已经由浏览器创建,而且处理函数引用的所有其他JavaScript代码都已被下载并求值。如果我们的应用需要其他初始化工作,例如创建一些簿记数据结构或执行一些DOM操作,又该怎么办呢?同时,在这里,我们需要确保dojo.requried引用的JavaScript资源和DOM树已经可用。上述所有问题可以用dojo.addOnLoad解决。

dojo.addOnLoad接受一个函数作为参数并且保证该函数在满足下述三个条件后立即执行。

q   DOM树被浏览器创建并可以被客户代码使用。注意,这并不表示所有资源(例如图片)都被载入。

q   所有的通过Dojo加载器请求的JavaScript资源都被载入。

q  
所有Dojo小部件都被解析完毕。我们在第2章中介绍了,Dojo使你可以在HTML代码中直接声明小部件。当你通过这种方式引用Dojo小部件时,Dojo必须解析HTML代码,并将内嵌的小部件替换为实际的HTML实现代码。如果djConfig.parseOnLoad的值为true,那么只要在浏览器载入了DOM树即可完成该工作,不过要在任何通过dojo.addOnLoad注册的函数被执行之前完成。

    

Alex如是说……
    

 

好东西、坏东西和IE这东西

Dojo事件系统试图解决最常见的浏览器事件系统实现的问题,但是如果所有浏览器都正确地实现了W3C DOM Level 2事件模型,我们就不需要这样做了。IE 6和IE 7甚至都没有尝试去实现W3C模型,但这也只是问题的冰山一角而已。

当事件处理函数包含循环引用,或当事件处理函数引用到一个对象,而该对象又指向处理函数所连接的DOM节点时,IE就会出现内存泄漏。这个问题一直存在,有时是意外发生的。当浏览器自身的对象与用户的JavaScript代码之间建立了一个循环引用链时,IE不会聪明到将引用计数减到0,即使两个对象都不再被其他对象引用。要解决这个问题就要跟踪所有的连接并在页面卸载之前手工取消连接,这样就可以使用内建的垃圾回收器完全地消除这些对象的引用。在IE 7中,微软实现了一个系统,尝试更加全面地消除引用,但是这个系统在处理那些“悬挂”在JavaScript对象空间中但未与可见文档关联的节点时,还是不能令人满意的。互联网社区一直坚定地等待着微软修复这个存在了十来年的问题。手工跟踪连接效率低下而且容易造成错误,如果你使用dojo.connect,它会帮你解决这个问题。

当然,对于相对简单且生命期较短的网页来说,浏览器资源泄漏并不要紧。你的对象不可能增长得很大,你的用户也不可能在你的应用程序上花费太多的时间,不会因泄漏问题累积而致使程序运行十分缓慢。而当编写高级应用程序时(尤其是单页面应用),资源泄漏会造成性能急剧下降以至于应用程序都无法使用。只要你使用Dojo的事件API进行连接,就完全可以不关心这种类型的浏览器错误。
    

 

 

dojo.onLoad可以被调用任意多次,所有作为参数传入的函数严格按照它们被注册的顺序执行。dojo.onload的调用参数可以是一个函数的引用或是一个对象的函数名。下面是每种用法的示例。

 


 

下面是事件示例的完整代码,其中包含对dojo.addOnLoad的调用:

<DIV>

 
</DIV>
    

Alex如是说……
    

 

使用dojo.addOnLoad而不使用DOM的onload

简单的页面可以通过注册windows对象的onload事件进行初始化,但是这并不具备可扩展性。如果其他代码也要监听onload事件呢?如果这些代码在你之后注册,你的代码会被执行吗?如何保证你的onload事件处理函数没有破坏别人的处理函数?我曾见过一个大的开发团队因为某人提交了一段window.οnlοad=function(){...}代码而被迫停工。Dojo的onload机制给你提供了一个简单的注册多个处理函数的方法,使用它可以解决令你头痛的问题。

这里有一个问题,onload对我们来说意味着什么。浏览器默认的onload事件直到页面中引用的所有的外部资源都加载后才触发。这意味着,我们必须等待所有的图片、对象、CSS文件和其余的下载。如果页面包很多或外部资源很大或网络速度很慢,用户看到的将是功能异常的页面(由于初始化代码还未执行),也许这还要持续很长一段时间。我们希望我们的小部件和进一步增强的功能尽可能快地开始展现出来,在理想的世界中,浏览器可能在页面构建阶段为我们提供所有这些阶段性的事件(oncssload、onimgload、ondomparsed,等等)。另外,有一些浏览器特定的修补技巧和权变措施使我们可以尝试确定,是否恰好在所有图形被完全解析之前我们想要的资源已经被“加载”,但是这也并不是我们想要的。如果你的代码依赖于布局的“稳定”,那么外部加载的一点CSS代码就会对此造成影响,我们的小部件可能看上去已经被“损坏”。我们真正想要实现的语义类似于“onlayoutstable”,大约是在所有的CSS规则已经被应用并且所有的图片也都有了尺寸(即使还没有实际内容的话)的时候。实际上并没有这样的原始事件,但是Dojo已经尽力为我们确定这一事件发生的时刻了,这正是通过dojo.addOnLoad注册的处理函数被调用的时刻。

如果你正在使用跨域的加载器,也就是说从AOL的CDN(内容分发网络)加载Dojo,那么你的网页需要的一些JavaScript资源可能在本地的onload事件已经被触发时还不可用。Dojo. addOnLoad在这一点上足够聪明,它在所有的模块都可用时才触发,无论你要使用什么包。

最后,如果你喜欢使用dojo.connect,你可以连接到dojo.loaded事件而不使用dojo.addOnLoad,因为只要你包含了dojo.js,这三个函数就都被定义了。
    

 

以上就是关于Dojo DOM事件框架的全部内容。从程序员的观点看,Dojo事件框架已经简化到极致了:

q   dojo.connect/disconnect连接/取消连接处理函数。

q   处理函数通常要接收一个与事件相关的事件对象以获取事件的详细信息。

q   使用事件对象的target/currentTarget属性获取目标/当前目标DOM节点,而不使用this。

q   能冒泡的事件通常都要冒泡,而捕获阶段通常被禁止。

q   定义了键盘事件及其关联的事件对象(这一点与W3C标准不同)。

q   利用dojo.addOnLoad对那些把处理函数连接到事件的初始化函数进行注册。

最重要的是以上所列内容都是浏览器无关的。

我们将所有你想要知道的关于DOM事件的内容分类放在图6-3中,作为本节的总结。

 

事  件
    

事件对象类型
    

是否冒泡
    

是否可被撤销
    

描  述

mousedown
    

MouseEvent
    

B
    

C
    

指示设备被按下

mouseup
    

MouseEvent
    

B
    

C
    

指示设备被释放

mousemove
    

MouseEvent
    

B
    

 
    

指示设备被移动

mouseover
    

MouseEvent
    

B
    

C
    

当指示设备移动到一个元素上时产生的

mouseout
    

MouseEvent
    

B
    

C
    

当指示设备被移动到一个元素之外时产生的

click
    

MouseEvent
    

B
    

C
    

由连续的mousedown和mouseup事件产生

(也就是说,mousedown和mouseup之间没有mousemove事件发生)

keydown
    

kbEvent
    

B
    

C
    

一个按键被按下

keyup
    

kbEvent
    

B
    

C
    

一个按键被释放

keypress
    

kbEvent
    

B
    

C
    

一个按键被按下或者被一直按着。一个keydown和keyup事件之间可以包含一到多个keypress事件

load
    

Event
    

 
    

 
    

浏览器完成了对文档、框架集中的框架或者一个对象元素的加载

unload
    

Event
    

 
    

 
    

浏览器完成了从一个视窗或框架中删除一个文档的操作

abort
    

Event
    

B
    

 
    

浏览器对图像的加载被中止

error
    

Event
    

B
    

 
    

发生图像载入错误或者脚本执行错误

select
    

Event
    

B
    

 
    

用户选择了一段文本

change
    

Event
    

B
    

 
    

一个控件的值被修改并失去了焦点

submit
    

Event
    

B
    

C
    

表单被提交

reset
    

Event
    

B
    

 
    

表单被重置

focus
    

Event
    

 
    

 
    

一个元素获得焦点,仅对表单控件有效

blur
    

Event
    

 
    

 
    

一个元素失去焦点,仅对表单控件有效

resize
    

Event
    

B
    

 
    

文档视图大小被重置

scroll
    

Event
    

B
    

 
    

文档视图被滚动

DOMFocusln
    

UIEvent
    

B
    

 
    

一个元素获得焦点,仅对表单控件有效

DOMFocusOut
    

UIEvent
    

B
    

 
    

一个元素失去焦点,仅对表单控件有效

DOMActivate
    

UIEvent
    

B
    

C
    

一个元素被激活

图6-3 事件目录

6.1.8 DOM事件分类

Dojo事件框架支持图6-3[4]中的所有事件。表中列出了每一事件的名称(这个名称作为event参数传给dojo.connect)、对引发该事件的原因的描述以及下面三个属性。

q   传给处理函数的事件对象的类型。

q   事件是否支持冒泡(B表示支持)。

q   事件相关的默认处理过程能否被取消(C表示可取消)。

现在,我们已经研究了DOM事件,你可能会问,事件的理念是否可以扩展到自定义对象上呢?Dojo给出了响亮的回答:可以!让我们来了解它是如何做到的吧。

[1]   有一种情况下,在捕获阶段进行事件处理是必需的:捕获鼠标(即截取所有的鼠标消息)。目前,Dojo还没有提供一个相关功能的公共接口,你必须直接实现鼠标捕获。

[2]   注意这里的提法。由于Dojo是从子节点一层一层调用父节点的处理函数,故子节点为低层,父节点为高层。换句话说,特化的处理函数是低层,泛化的处理函数是高层。——译者注

[3] 当然你就只需写dojo.connect(node,”click”, node, f)就可以强制指定node作为上下文。

[4]   实际上,所有这些事件都是由浏览器触发的,Dojo正是利用了这一点。在某些浏览器中,有一些键盘事件是Dojo合成触发的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值