这些代码会确实如你所设想的那样运作:鼠标移到
事件的一般特性
JavaScript事件很好一面是,它们有着一些相对一致的特性,给予你开发时的更多的能力和控制。最简单和最古老的概念是事件对象,它给你一系列的 元数据和上下文相关的函数,允许你处理诸如鼠标事件和键盘按键事件等。另外,有一些函数用来修改事件的通常的捕获/冒泡流程。深入学习这些特性可以让你事 半功倍。
事件对象
事件处理函数的一个标准功能是以某种方式访问包含当前事件的上 下文信息的事件对象。这一对象在特定的事件中充当着非常有用的资源。比如,当处理键盘按下的事件时,你可以访问事件对象的keyCode属性,以得知被按 下的是哪的键。在附录B中可以找到关于事件对象的更详细的说明。
然而,事件对象棘手之处在于,IE的实现与W3C的规范并不相同。IE有一个 单独的全局事件对象(可以可靠地通过全局属性window.event访问),而其它的每一种浏览器都把事件对象作为单个的参数传递给事件处理函数。可靠 地使用事件对象的一个例子见程序6-4,代码修改一个普通的
事件对象包含了大量的属性和函数,且它们的命名与行为在浏览器之间各不相同。我不想现在就进入那些细节,但是我强烈建议你阅读附录B,那是所有的事件对象属性的一个的列表,包括使用方法以及实际使用中的例子。
this关键字
this关键字(见第二章)提供了一种在函数作用域中的访问当前对象的方式。现代浏览器使用this关键字给所有的事件处理函数提供上下文信息。它们中 只有一部分(而且只有部分方法)良好地运行,将它设为当前对象;这将很快地被深入讨论到。例如,在程序6-5中,我可以利用这一事实,只建立一个通用的函 数来处理所有点击而通过this关键来确定作用于哪一个元素,它将如预期地工作。
程序6-5. 点击时改变
this关键字的确只是为了方便而设的,但我想你会发现,当使用它的属性,将会极大地降低你的JavaScript代码的复杂性。在本书中,我将试图使用this关键字编写所有的事件相关的代码。
取消事件冒泡
知道了事件的捕获/冒泡怎样工作以后,我们再来探讨怎样控制它。前面的例子里引入的一个很重要的问题是,如果你想要一个事件只在其目标上而不在基父级元 素上出现,你处配办法停止它。阻止事件冒泡的过程将导致出现如图6-4所示的情形,在其中被第一个元素捕获的事件的后续的冒泡被取消。
图6-4. 第一个元素所捕获的事件被取消的结果
停止事件的冒泡(或捕获)被证明在复杂的应用程序中是极其有用的。不幸的是,IE提供了一种与所有其它浏览器不同的方式来阻止事件冒泡。程序6-6是一 个通用的取消事件冒泡的函数。该函数接受单个参数:传递到事件处理程序的事件对象。该函数处理取消事件冒泡的两种方式:标准的W3C方式和非标准的IE方 式。
程序6-6. 停止事件冒泡的通用函数
现在你可能想知道的是,什么时候我想要阻止事件冒泡?老实说,多数时间里你可能从来不需担心这个。当你开始开发动态的应用程序(尤其是需要处理键盘和鼠标事件)时,这一需求才会变得突出。
程序6-7展示了一个简明的代码片段:为你鼠标悬停的当前元素加上红色边框。如果不阻止事件冒泡,每一次你把鼠标移动到一个元素时,该元素及其所有的父级元素都将有一个并非我们想要的红色的边框。
程序6-7. 使用stopBubble()创建一系列交互式的元素
拥有阻止事件冒泡的能力,你就能对事件到达哪个元素并进行处理有了完全的控制。这是开发动态的web应用程序所需的一个非常工具。最后一点,取消浏览器的默认动作,这允许你完全改写浏览器的行为并实现新的功能以替代之。
改写浏览器的默认动作
对于发生的大多数事件,浏览器有一些总会发生的默认动作。比如说,点击一个元素将会把你带到它所关联的网页;这是浏览器的一个默认动 作。这一动作总是在事件的捕获和冒泡阶段都完成以后发生,如图6-5所示。该示例说明了用户在页面点击元素的结果。事件起初经历在 DOM中的捕获和冒泡阶段(如前所述)。然而,一旦事件完成了其旅程,浏览器将试图执行该事件及元素的默认动作。在这里也就是访问链接的网页。
图6-5. 事件的完整生命周期
默认动作可以概括为浏览器所执行的你没有明确指定的操作。下面是特定事件发生时几种不同类型的默认动作:
a. 点击一个元素将会跳转到元素的href属性指定的URL。
b. 按下Ctrl+S键,浏览器将试图保存当前网页。
c. 提交一个HTML
使用stopDefault函数,你现在可以阻止浏览器给出的任何默认动作。这允许你用脚本为用户编写出灵巧的交互,如程序6-9所示。此代码使一个页 面内所有的链接在一个自包含的
绑定事件监听器
怎样将事件处理程序绑定到元素是JavaScript里一直以来不断推进的追求。起初,浏览器强制用户将处理代码内联地写在HTML文档中。好在那一些技术已经变得远远过时了(说这是一件好事,是考虑到它与非侵入的DOM脚本里数据抽象的精神相悖)。
当IE与NetScape激烈竞争的时候,它们各自开发出两个独立但又非常相似的注册事件的模型。最终NetScape的模型被修改成为W3C标准,而IE的则保持不变。
于是乎,目前存在三种可用的注册事件的方式。传统方式是老式的内联附加事件处理函数方式的一个分支,但是它很可靠而并能一致地工作。另外两种是IE和 W3C的注册事件的方式。最后,我将给出一套可靠的方法,开发者可以用它们来注册和注销事件而不需再担心底层是什么浏览器。
传统绑定
传统的绑定事件的方式是我在本章中到目前为止所一直使用的。它是到目前为止最简单最兼容的绑定事件处理程序的方式。使用这种方式时,你只需将函数作为一个属性附加到你想要监视的DOM元素上。6-10展示了使用传统方式绑定事件的一些例子。
程序6-10. 使用传统的事件绑定方式附加事件
这一技术有一系列的优势和缺点,使用时必须注意。
传统绑定的优势:
a. 使用传统绑定的最大的好处在于它无比地简单和一致,也就是说在很大程度上它能保障无论使用什么浏览器都能生效。
b. 当处理事件时,this关键字指向当前的元素,这一点是非常有用的(如程序6-5示范的那样)。
传统绑定的缺点:
a. 传统绑定只作用于事件冒泡,而非捕获和冒泡。
b. 只能每次为一个元素绑定一个事件处理函数。当使用流行的window.onload属性时,这将会潜在地导致令人困惑的结果(因为它会覆盖其它的使用相同 方法绑定的代码片段)。程序6-11展示了这一问题的一个实例,一个新的事件处理函数覆盖了原来的事件处理函数。
c. event对象参数只在非IE浏览器上有效
程序6-11. 事件处理函数互相覆盖
懂得了盲目覆盖其它事件的可能性,你可能会选择只在可以信任所有其它的代码简单的情况下使用传统绑定。解决这一混乱情况的一种方式是使用浏览器提供的现代绑定方法。
DOM绑定:W3C
W3C的为DOM元素绑定事件处理函数的方法是这方面唯一真正的标准方式。除了IE,所有其它的现代浏览器都支持这一事件绑定的方式。
附加新的处理函数的代码很简单。它作为每一个DOM元素的名为addEventListener的方法存在,接收3个参数:事件的名称(如 click),处理事件的函数,以及一个来用使用或禁用事件捕获的布尔标志。程序6-12展示一个实际使用addEventListener的例子。
程序6-12. 使用W3C方式绑定事件处理函数的示例代码片段
W3C绑定的优势:
1. 这一方法同时支持事件处理的冒泡和捕获阶段。事件的阶段通过设置addEventListener的最后一个参数为false(指示冒泡)或true(指示捕获)来切换。
2. 在事件处理函数内部,this关键字引用当前元素。
3. 事件对象总是作为事件处理函数的第一个参数被提供。
4. 你可以绑定任意多个函数到一个元素上,而不会覆盖先前所绑定的。
W3C绑定的缺点
1. 它在IE里面无效。你必须使用IE的attachEvent函数来代替。
如果IE采用了W3C的方法来绑定事件处理函数,这一章将会比现在短得多,因为那将会不再需要讨论绑定事件的替代方法。然而,到目前为止,W3C的事件绑定方法仍然是最可理解和最易使用的。
DOM绑定:IE
在许多方面,IE的绑定事件的方式看起来跟W3C的非常相似。但是,当你触及细节的时候,它又在某些方面有着非常显著的不同。程序6-13是IE中绑定事件处理函数的一些例子。
程序6-13. 使用IE的方式绑定事件处理函数的示例
IE绑定的优势
1. 你可以绑定任意多个函数到一个元素上,而不会覆盖先前绑定的。
IE绑定的缺点
1. IE只支持事件的冒泡阶段。
2. 事件监听函数内部的this关键字指向window对象,而非当前函数(这是IE的巨大败笔)。
3. 事件对象只能从widnow.event得到。
4. 事件名称必须形如onxxxx——比如,要使用"onclick"而不能只是"click"。
5. 它只对IE有效。对于非IE平台,你必须使用W3C的addEventListener。
相对其半标准的事件特性,IE事件绑定的实现是严重短缺的。鉴于它的许多不足之处,弥补方案必须继续存在以强制它合理的运转。然而,幸运的是,向DOM添加事件的通用的函数确实存在,它能极大的减轻我们的痛苦。
addEvent和removeEvent
2005年末,Peter-Paul Koch( http://quirksmode.org) 发起了一个竞赛,向JavaScript代码编写者公开征求一对新的函数,addEvent和removeEvent,用来提供一个可靠的方式为DOM元 素添加和删除事件。最终我以一段非常简练而又能足够好地运行的一段代码在其中获胜。但是,后来,其中一位裁判(Dean Edwards)给出了函数的另一个版本,远远地超越了我所编写的。它的实现使用传统的方式来附加事件处理函数,完成忽略现代方法。因此,它可以在大量的 浏览器上运行,而仍然提供了必要的事件的优美性(比如this关键字和标准的事件对象)。程序6-14展示的一段示例代码很好的利用新的addEvent 函数,使用了事件处理的所有的不同侧面,包括浏览器默认动作的阻止,正确的事件对象的引入,和正确的this关键字的引入。
程序6-14. 使用addEvent函数的示例代码片段
addEvent函数提供了一个绝妙的简单而强大的方式来处理DOM事件。只要看看优势和不足,就可以明显地看出这一函数可以作为一致而可靠的方式来处 理事件。程序6-15是完整的源代码,它能在所有的浏览器中运行,不泄露任何内存,处理了this关键字和事件对象,并标准化了事件对象。
程序6-15. Dean Edwards编写的addEvent/removeEvent库
addEvent函数的优势
1. 它可以在所有浏览器上工作,甚至是很老的不被支持的浏览器
2. this关键字对所有的绑定的函数可用,指向当前元素
3. 浏览器特有的阻止浏览器默认动作和停止事件冒泡的函数都被统一了
4. 不管是哪种浏览器,事件对象总是作为第一个参数传给处理函数
addEvent函数的缺点
1. 它只能工作于冒泡阶段(因为它在底层使用了传统事件绑定方法)
考虑到addEvent/removeEvent函数是如此的强大,绝对没有理由不在你的代码中使用它们。在Dean的代码的基础上,添加一些诸如更好的事件对象标准化、事件触发、大量事件删除这类在通常的事件结构中原本极难的事情委实是轻而易举。
事件的类型
JavaScript事件可以被归入几种不同的类别。最常用的类别可能是鼠标交互事件,然后是键盘和表单事件。下面的列表提供了web应用程序中存在并可被处理的不同各类的事件的粗略预览。参考附录A和附录B,可以得到大量的事件的实例。
鼠标事件: 分为两种,追踪鼠标当前位置的事件(mouseover,mouseout),和追踪鼠标在哪儿被点击的事件(mouseup,mousedown,click)。
键盘事件: 负责追踪键盘的按键何时以及在何种上下文中(比如说,追踪一个form元素内的按键相对于出现在整个页面的按键)被按下。与鼠标相似,三个事件用来追踪键盘:keyup,keydown,keypress。
UI事件: 用来追踪用户何时从页面的一部分转到另一部分。例如,使用它你能够可靠地知道用户何时开始在一个表单中输入。用来追踪这一点的两个事件是focus和blur(用于对象失去焦点时)。
表单事件: 直接与只发生于表单和表单输入元素上的交互相关。submit事件用来追踪表单何时提交;change事件监视用户向元素的输入;select事件当