原文地址:http://www.quirksmode.org/js/introevents.html

“事件”简介

“事件”可谓世上任何JavaScript程序跳动的心脏,在这篇文章中,我将概述一下何谓“事件处理”,同时,也会谈到它的所存在的问题以及怎么写去编写跨浏览器的兼容脚本。我也将提供一些页面,来讲述“事件处理”的一些高级特性。

没了“事件”就意味着没有脚本。简略去看一下那些运用了JavaScript的网站页面:几乎所有的情况都是需要一个“事件”去触发相应的脚本块代码。理由很简单,JavaScript意味着给你的页面带来交互:用户做了一些事,页面做出了反应。

因此,JavaScript需要一个方法去侦测用户的行为以便于它知道何时去做出反应。它一样需要知道哪些函数需要被执行,那些你(开发者)认定可以为你的页面吸引人眼球的函数。这些页面描述了编写脚本最好的方式。这并不简单,但是这却是一件非常令人兴奋的事。

当用户做了一些事导致“事件”被触发了。另外也会有一些“事件”不会直接因为用户所能触发的:比如,“加载事件”只有在页面被加载完成之后才会(自动)触发。

JavaScript会侦测这些“事件”。回到Netscape 2之前的时代,(那个时候)是可以为一个确定的HTML元素(早些时候,大多数链接和表单)添加“事件处理器”,这些“事件处理器”等待着各自对应的“事件”,比如像点击一个链接之类的(事件)。当这(点击事件)发生时,这个处理器会执行你预先定义好的JavaScript代码。

当用户做出动作时,他也产生了“事件”,当你的脚本让你的页面为这个“事件”做出了反应时,“交互”应运而生。

“事件处理器”的历史

就像我说的,没有了“事件处理”,在你的页面里面添加JavaScript也就没有任何意义了。最好的脚本就是能够为你的用户所做的事情做出反应。因此,当Netscape发布了它的第二个版本之后,也就是支持JavaScript的版本,它也同时就支持了“事件”。

Netscape模型

Netscape 2仅仅只支持一小部分“事件”。“鼠标划过”和“鼠标划入”立马变得十分有名,因为通常“鼠标划过”的效果,onMouseOver可以改变图片,而onMouseOut则又能变回图片。这也让看见用户是否提交或者重置一个表单成为了可能,因此客户端的验证也变成了可能。浏览器同样也可以侦测一个表单域是否填入失败,或者失去了焦点,或者这个页面是否已经完全加载,或者说开始卸载了。虽然通过呈现标准是一个非常基本的行为,但是与此同时它却对页面而言是一个具有革命性的可能性扩展。真正的交互开始变成了可能,因为你可以为用户的行为作出反应了。

在像这样最为古老的“事件处理器”中。当用户点击了链接,“事件处理器”被执行,同时也弹出警示框。

<a href="somewhere.html" onclick="alert('I\'ve been clicked!')">

非常值得注重的是,这种古老的“事件处理”方式,实际上是由Netscape来标准化的。所有其他的浏览器,包括IE浏览器,如果他们想让JavaScript工作,必须遵从这种Netscape 2、3的“事件处理”方式。因此,这些古老的“事件”和“事件处理器”(模型)是能在所有的能运行JavaScript的浏览器中运行的。

现代事件模型

无论怎样,迄今自推出这些简单的“事件处理器”,它们早已改变了。首先从数量上,这些“事件”类型就已经增加。再者,通过向HTML元素注册“事件处理器”的方式也已经改变。它们现在可以完全用JavaScript来进行设置。再也不需要一大堆“事件处理器”堆放在你的代码中,现在你可以仅仅写一段简单的脚本用来设置所有的“事件处理器”了。

那些第四个版本的浏览器同样也提供了更多关于“事件”本身的信息。当“事件”发生时,鼠标在哪?有任何的键盘按键按下吗?总之,当一个元素和他的父元素同时拥有一个同样类型的“事件处理器”的时候,浏览器厂商此刻必须决定发生了什么。哪一个(元素拥有的)“事件”最先触发?

在浏览器大战的顶峰时,自从这个功能被增加,Netscape和微软做了一个明确的不同点,他们各自创建了一套完全不相容的“事件模型”。更近期,当W3C组织发布了它的“DOM事件”规格说明书,一个第三方的(“事件”)模型又出现了。W3C的模型低耦合地基于老的Netscape模型,尽管这是一个严重的缺陷,但是它要更广泛和实用,添加大量新颖有趣的功能并且解决一大堆老的“事件模型”存在的问题,这是一份十分有挑战的工作。

无可厚非,存在这三种模型意味着“事件处理”在所有的浏览器工作的方式也会不相同。

浏览器兼容问题

我们又到这儿了。就像通过DHTML一样,W3C DOM或者其他高级的脚本技术,我们必须小心翼翼地运行特定的代码位,仅仅在那些能识别它们浏览器里面。如果在IE浏览器里面调用stopPropagation()或者在Netscape里面使用srcElement,将会出现一些可怕的错误并且使我们的代码变得毫无用处。因此,我们需要首先检测浏览器是否支持这些我们想要用到的方法或者属性。

但是,简单的代码分支就像这样:

if (Netscape) {
	use Netscape model
}
else if (Explorer) {
	use Microsoft model
}

这仅仅是一个简略的解决方案,因为它把很多不常用的浏览器给排除在外了。最新的浏览器可以处理大量的现代“事件处理”,当然你的脚本无比地聪明,能决定那些不常用的浏览不会允许,甚至尝试运行这些代码,因为他们不是Netscape或者IE。

所有的非主流浏览器不得不为选择支持哪种“事件模型”而做出艰难的决定。Konqueror/Safari,通常会选择服从严格的标准并且支持W3C模型。Opera和iCab则更加小心地支持老的Netscape模型和微软模型中大量的部分。此外,我也没有研究更不怎么常用的浏览器了。

但是,就算一个实在不怎么常用的浏览器,也很有可能会支持微软获取事件的方式,与此同时,那个实际的“事件”属性混合了W3C和老的Netscape模型(的标准)。通过自己的方式紧跟这些主流浏览器的常用标准,这是没有任何问题的。你的脚本,必须为此做好准备。

请不要使用浏览器侦测技术

首先,把你打死了也不要使用浏览器侦测技术。这是自寻短见最快的方式了:(。使用navigator.userAgent去检测“事件模型”的任何脚本,简直糟糕透顶,并且你将会在嘲笑中被蔑视。

其次,不要被DHTML的对象检测和“事件”对象检测搞迷糊了。当编写DHTML,我们通常通过询问检测DOM是否支持,比如,当通过判断if(document.all)是否被支持。如果这样,一份脚本段可以安全地执行微软的all容器。

但是DHTML和“事件处理”有不同的浏览器兼容模式。比如,Opera 6是支持部分W3C DOM标准的,但是并不遵从W3C的“事件模型”。因此,DHTML对象检测将会在Opera中执行错误的“事件”代码分支。因此脚本使用到if(document.layers)和对于这样的“事件模型”侦测都是不正确的。

正确的提问

此刻我们将要做什么呢?这些“事件”属性的名称是最为严重的问题。如果我们在此处用了大量的特殊对象检测,我们将解决99%的兼容问题。仅仅找到鼠标的位置,是十分困难的,然而访问其他信息位却是相对比较简单的。

此外,尽量是不要去考虑三种所谓的“事件模型”。取而代之,我们必须去了解四种“事件”注册模型,两种“事件”获取模型,还有两种“事件”次序。查看快速“事件”兼容表,可以鸟瞰一下“事件处理”的兼容性。

现在,这听起来非常复杂,但并非如此。事实上,当我发现它的时候,我才开始真正了解“事件处理”。这是所有关于提出的正确问题。请不要问“我该怎么写一个‘事件处理’的脚本?”尽管这是一个正确的问题,但是这个却十分难以回答——这将要我花11个页面才能解答。取而代之,你需要问更多有特定答案的特定的问题:

  • “到底有哪些‘事件’类型?” 一大堆,当然,有些“事件”是某些浏览器不支持的。
  • “我该如何为一个HTML元素注册‘事件处理器’?” 有四种方式可以做到:内联传统W3C和微软,第一种方式在所有浏览器中都能运行,在这没有任何问题。
  • “我该如何为‘事件’阻止默认的行为?” 如果你在“事件处理”的脚本中使用return false,这些默认行为(包括链接、表单的提交按钮)会被阻止,这个技术被Netscape 2所标准化,并且至今工作良好。
  • “当我想知道更多信息的时候,我该如何访问‘事件’?” 这里有两种方式可以做到:W3C/Netscape和微软。为了解决这个兼容问题,你需要一行代码
  • “一旦我成功访问到了这个‘事件’对象,我该如何读到它的属性?” 这里有一些兼容上的问题在这儿,不过没关系,在这里的页面做了解释。你需要一个很棒的“事件”属性兼容列表和严格的对象检测。
  • “如果一个元素和他的祖先元素有一个‘事件处理器’,并且他们有相同的‘事件’类型,哪一个‘事件’会先触发?”——好吧,我怀疑你确实曾经问过这个问题。 但是这些各种各样的模型虽然提供了解答。有两种“事件”次序,“事件捕获”和“事件冒泡”。在日常的工作中,这些并不重要,除了在某些特殊情况中,你主要担心的是如何把他们关掉,这个是要通过两行代码解决的。

所有以上的问题将会在分开的页面中得到解答,那些页面给到了背景信息和“事件处理”细节上处理方法。

跨浏览器“事件处理”脚本的戏法不是用来做一个全“事件模型”检测,而是用来分别回答这些问题的。当读出“事件”属性时,你将发现你主要需要关心浏览器的兼容性。

首先选择一种“事件”注册模型,其次保证所有的浏览器都能访问到“事件”对象,再次读取到正确的属性并且此刻解决“事件”顺序问题——如果发生了。这样你可以分别解决每一个兼容问题,并且保证你的代码能运行在所有的浏览器上,只要浏览器支持高级的“事件处理”。

接下来

如果你希望把所有的“事件”页面按顺序过一遍,你需要下一步看看“事件”页面。

编写一个“事件处理”脚本

因此你该怎么去写一个“事件处理”脚本呢?在这个页面,我为那些希望快速得到答案的人和希望去学习真理的人,给出了一个快速的概述。

注册一个“事件处理器”

第一步就是注册你的“事件处理器”,你必须保证浏览器在你无论何时选择“事件”触发时能执行你的脚本。

有四种方式可以注册“事件处理器”:内联传统W3C和微软

最好使用传统的模型,因为它是完全跨浏览器兼容,并且给予了许多的自由与灵活性。为了注册“事件处理器”,如是:

element.onclick = doSomething;
if (element.captureEvents) element.captureEvents(Event.CLICK);  

现在函数doSomething()被注册成了HTML元素element的点击事件的“事件处理器”。这就意味着无论用户何时点击这个element,doSomething()会被执行。

获取HTML元素

有时候你也想获取“事件”发生所在的HTML元素。这里有两种方式:使用this关键字或者使用target/srcElement属性。

function doSomething(e) {
	if (!e) var e = window.event
  	// e refers to the event
  	// this refers to the HTML element which currently handles the event
  	// target/srcElement refer to the HTML element the event originally 		took place on
}

这个target/srcElement属性包含了一个指向触发该“事件”的原始HTML元素的引用。灰常有用,就算是当这个“事件”被捕获了或者冒泡终止,了这个target/srcElement不会改变:它仍然是“事件”原始触发的那个元素。(请看针对target/srcElement的“事件”属性页面,查看针对this关键字的页面在

读取属性

读出一些有趣的“事件”属性,这是浏览器兼容性最糟糕的领域。研究这个“事件”兼容表,并且编写你自己的脚本去读取你想要的信息。

确保尽可能地使用最详细的对象检测。首先应该检测每一个属性是否存在,然后读取它的值,例如:

function doSomething(e) {
	if (!e) var e = window.event
	if (e.keyCode) code = e.keyCode;
	else if (e.which) code = e.which;
}

现在,在所有浏览器,code变量可以获取被按下的key值了。

事件顺序

最后,你必须决定你是否需要“事件”冒泡了,如果你不希望它发生,阻止“事件”的冒泡。

function doSomething(e) {
	if (!e) var e = window.event
	// handle event
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
}

编写脚本

现在,你可以真实地开始编写“事件处理”脚本了。使用之前给你的那些让你决定实际中何时需要发生并且你的脚本应该对此如何做出反应的代码片段。谨记:保证正确的交互逻辑是最为主要的,不然你的用户都不知道到底发生了什么。