javascript事件模型解析

日期: 2012-05-03 来源:TechTarget中国 英文

  事件使得客户端的JavaScript有机会被激活,并得以运行。在一个Web页面装载之后,运行脚本的唯一方式,就是响应系统或者用户的动作。虽然从第一个支持脚本编程的浏览器面世以来,简单的事件被实现为JavaScript的一部分;但是大多数最近出现的浏览器都实现了强壮的事件模型,使脚本可以更加智能地处理事件。现在的问题在于:为了支持各种浏览器,您必须和多个先进的事件模型做斗争,准确地说,是三个。

  这三个事件模型分别和下面的文档对象模型(Document Object Model,即DOM)三巨头结盟:Netscape Navigator 4 (NN4),Macintosh和Windows系统的Internet Explorer及其更新版本(IE4+),以及在Safari中得到实现的W3C DOM。尽管这些模型之间有些地方存在一些本质的差别,但是在一些简易的JavaScript的帮助下,它们都可以同时适用于同一个文档。本文主要着眼于相互冲突的事件模型中的两个关键方面:

  把一个事件和HTML元素绑定起来的方法。

  在事件被触发后如何对之进行处理。

  事件绑定的方法

  事件绑定是指构造一个响应系统或者用户动作的HTML元素的过程。在不同的浏览器版本中,有不少于五种事件绑定技术。下面我们快速地介绍一下这些技术。

  事件绑定方法I:绑定元素属性

  最简单和向后兼容性最好的事件绑定方法是把事件绑定到元素标识的属性。事件属性名称由事件类型外加一个“on”前缀构成。尽管HTML属性并不是大小写敏感的,人们还是定义了一个规则,规定事件类型的每一个“词”的首字母大写,比如onClick和onMouseOver。这些属性也被称为事件处理器,因为它们指示了元素如何“处理”特定的事件类型。

  正确的事件处理器属性的值在形式上是被引号包含的JavaScript语句。最常见的值是一条调用某个脚本函数的语句,而被调用的函数在位于文档前部的……

  事件绑定方法II:绑定对象属性

  对于NN3+和IE4+这两类浏览器,脚本编程人员可以以脚本语句的方式把事件绑定到对象上,而不是绑定到元素标识的属性上。每一个负责事件响应的元素对象都为自己能够识别的事件设置了相应的属性。对象属性名称是元素标识属性的小写形式,比如onmouseover。NN4还接受interCap(即首字小写,之后的每一个词的首字大写)版本的属性名,但是考虑到跨浏览器的兼容性,所有字母都是小写的名称会更安全一些。

  当您把一个函数的引用赋值给一个事件属性的时候,就发生了绑定。函数的引用是指函数的名称,但是不带函数定义中的括号。因此,如果要为一个名为myButton的按键的点击事件(click)进行绑定,使之激活一个定义为myFunc()的函数,则其赋值语句如下所示:

  document.forms[0].myButton.onclick = myFunc;

  您应该注意一点:在事件触发的时候,没有办法向事件函数传递参数。本文在稍候对事件处理过程的讨论中还会回顾这个问题。

  事件绑定方法III: 绑定IE4+

  当然,标识中的语句可以调用页面上其它地方定义的任何函数(或者从.js文件中导入的函数)。然而,这种绑定方式意味着您必须为每一个元素和每一个事件创建一个。

  那种绑定方法最好?

  如果您足够幸运,只需要为某一个操作系统上特定版本的浏览器创建应用程序,则可以为选定的浏览器选择最现代的绑定方式。但是对于跨浏览器的网站作者来说,选择绑定方法则需要面对实质性的挑战。

  如果您只计划支持IE5/Mac,则可以不考虑attachEvent()和addEventListener()方法,因为IE5/Mac对这两种方法都不支持。这种情况下,比较实际的选择有两种,要么绑定标识属性,要么绑定对象属性。这时就需要费心思了。

  一方面,W3C DOM Level 2承认基于标识属性的方法,并将它推荐为addEventListener()方法的可接受代替方法。为了和数以百万计的脚本相兼容,所有支持脚本编程的浏览器都支持基于标识属性的事件绑定方法。一些自动化的页面制作工具,比如DreamWeaver,也把事件处理器的属性嵌入到HTML标识中。

  但是另一方面,在元素标识文件中嵌入面向脚本的信息,又不能将内容从风格及行为中分离开来,这和当前的流行趋势相违背。把事件绑定到对象属性上的方法听起来方向是对的,但是在W3C关于HTML,XHTML,或者DOM的标准中,并没有对事件属性提供“官方”的支持。尽管如此,在实际生活中,除了第一代支持脚本编程的浏览器之外,其它浏览器都支持这种方法。

  一个纯标准论者会认为上述的两种方法都有缺点,但是对于讲究实际的开发者来说,即使考虑到未来主流浏览器的兼容性,这两种方法都是“安全”的。

  事件的信息矿:事件对象

  所有这三种事件模型的核心都是一个事件对象–它是一个抽象的实体,其属性中包含很多对事件处理函数具有潜在价值的信息。从本文早些时候对事件绑定技术的讨论中,您可能可以推断出事件对象对脚本之所以至关重要,原因之一是除了基于标识属性的绑定方法以外,其它绑定方法都不支持将参数传递到事件处理函数中。

  事件对象通过提供足够的“挂钩”,使事件处理函数可以读取事件的特征,从而填补了这个缝隙。因此,事件处理函数可以得到接收事件的元素的引用,以及其它一些有用的信息,比如鼠标动作的坐标,鼠标使用的按键,键盘上被按压的键,以及在事件发生的过程中是否有修饰键被按下(比如检测Shift-click事件)。

  访问事件对象

  虽然事件对象的精确构成因为本文讨论的三种DOM(NN4,IE4+,以及 W3C/Safari)的不同而有所变化,但是,一个事件处理函数只能通过以下两种方式之一来访问事件对象:NN方式和IE方式。W3C/Safari DOM事件对象公布给脚本的接口方式和NN4的事件对象一样;而IE4+则有自己的方法。

  IE4+的事件对象更加易于描述,因此我们首先对它进行讨论。简单地说,事件对象是window对象的一个属性。这意味着在所有的实例中只有一个事件对象。举例来说,在键盘上简单地按压和松开一个按键,会产生三个事件:onKeyDown,onKeyPress,和onKeyUp(事件的发生顺序和这里的列举顺序相同)。如果onKeyDown事件激活的函数花费很长的时间进行处理,则浏览器就会把其它两个事件保持在队列中,直到onMouseDown 事件处理完成为止。

  而对于NN4和W3C DOM来说,事件对象看起来就更加抽象一些。除了基于标识属性风格的绑定方法之外,其它绑定方法都是把事件对象自动传递给与事件相绑定的函数。传递给函数的是一个单一的参数。开发者需要在函数中定义一个参数变量,来“接收”该参数的值。为了避免和IE中的window.event对象互相冲突,请不要把参数命名为event。举例来说,把它命名为evt就相当好,相应的事件函数的定义大致如下:

  function myFunc(evt){ // script statements here }

  然而,如果您使用的是基于标识属性的事件绑定技术,就必须显式地把事件作为一个参数传递到您调用的函数。为了完成事件的传递,需要把event这个关键字作为参数进行传递:

  onClick = “myFunc(event)”

  外部传入的参数是您的事件处理函数和NN的事件对象之间的唯一联系纽带。如果在主事件处理函数内部调用的其它函数需要该对象或者该对象的属性值,则您可以把该对象或其属性值作为参数中继给这些函数。

  如果您想知道 IE 是否把事件的引用保存在window.event属性中,那答案是“是”。使用这个语法交集是相当安全的,因为在NN和IE这两个浏览器,被传递到事件处理函数的事件对象都有您所期望的当前事件的属性值。

  兼容两种事件对象引用

  设想在处理事件时,我们需要在一个事件函数中考察一个或者多个事件属性。这是一个简单的技术,可以使事件处理函数和作为参数传入的事件对象协同工作,或者从window.event属性中读取信息。而且,这个技术不必处理不同的浏览器版本之间的细微差别。

  在开始的时候,需要在您的事件处理函数中定义一个参数变量,准备接收可能传入的事件对象。然后,通过简单的条件表达式把浏览器的事件对象赋值给上述的参数变量:

  function myFunc(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) // process event here }

  如果事件对象真的以参数的形式传进来了,则在函数内部,事件对象就被保留在evt这个局部变量中。如果这个参数是null,而且浏览器的window对象包含有一个event属性,则window.event对象就会把自己赋值给evt变量。

  然而,为了完成这个工作,还应该再包含一层或者多层条件控制,以便优雅地适应那些在事件模型中没有定义事件对象的的早期浏览器:

  function myFunc(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) if (evt) { // process event here } }

  为了把同样的方式应用到所有事件处理函数的构建中,您可以定义一个函数来兼容两种事件,即由绑定的标识属性显式传入的事件对象,以及由绑定的事件属性隐式传入的事件对象。这样即使您在开发过程中改变了事件绑定的风格,这个函数也不必改变。

  瑞典自助餐式地选择事件对象

  然而,建立一个指向事件对象的引用只是战斗的一部分。来自不同事件模型的每一个事件对象都拥有自己的一套属性,以容纳事件的细节。下面的表格列出了最常用的属性,以及这些属性在上述三种事件对象类型中的名称。

  表格 1. 流行的事件对象属性

  描述 NN4 IE4+ W3C/Safari

  Event target target srcElement target

  Event type type type type

  X coordinate on page pageX * pageX

  Y coordinate on page pageY * pageY

  Mouse button which button button

  Keyboard key which keyCode keyCode

  标注*的属性值可以通过对event.clientX + document.body.scrollTop或者event.clientY + document.body.scrollTop进行求值来得到。

  Macintosh版本的IE5在通常情况下都遵循IE4+的事件对象模型,但是有一个例外,即IE5/Mac的事件对象既定义了srcElement属性,也定义了target属性,这两个属性都指向接收事件的元素。

  需要抽象的最重要的事件对象属性可能得算指向接收事件的HTML元素的引用。NN4和W3C的事件对象采用相同的属性名(target),而IE4+的事件对象则使用srcElement属性。这时候,对象检测技术(而不是费力劳神而又具有危险倾向的浏览器版本识别方法)再次拯救了我们。对于那些非文本容器的元素,一个简单的条件表达式就可以轻松处理脚本语法上的差别:

  var elem = (evt.target) ? evt.target : evt.srcElement

  从现在开始,您的脚本就可以读写任何浏览器对象模型公布出来的元素对象属性了。

  W3C DOM结点的事件目标

  W3C DOM的结点架构使得文档中的每一个结点都可以接收事件。在支持这一架构的浏览器中,发生在嵌套文本顶上的事件并不调用分配给文本容器的事件处理器,相应的文本结点才是该事件的目标结点。考虑如下场景:

  在事件实例,当鼠标的指针在一个SPAN元素包含的文本顶上滚动时,该文本就会被高亮显示。事件绑定的过程通过对象属性在init()函数中进行。从表面上看,当用户在SPAN元素顶上滚动鼠标时onMouseOver事件动作函数就为该元素指派一个与风格表单规则相关联的类名(highlight),该风格规则把文本的显示风格定义为粗体,黄色背景;而在onMouseOut函数中,则把风格恢复为原始的版本(类normal)。请注意一个toggleHighlight()数是如何在事件对象的type属性的帮助下,执行两个动作的(该属性在所有事件模型对象中的名称是相同的)。请试一下这个事件实例。

  但是如果您把例子装载到NN6,则鼠标事件的真正目标就是SPAN元素中的文本结点了。本文并不讨论事件的传播机制,但是请相信,W3C DOM事件模型的缺省行为会使事件沿着结点的包含层次向上传播(和IE4+中事件通过元素容器向上传播的机制很类似)。因此,在这个事件实例中。鼠标事件会从其真正的目标向上传递到文本结点的容器(也就是SPAN元素)。这些事件触发了SPAN元素中相应的事件处理器。

  虽然事件处理器属于SPAN元素,事件对象还是保留文本对象的引用,并将它作为事件的原始目标。然而,只有对文本结点的容器进行动作,才能修改它的风格。为了实现toggleHighlight()函数的等价操作,使之可以修改SPAN容器的className属性,该函数需要派生出一个指向文本结点容器的引用。

  一个策略是使用W3C DOM事件对象的currentTarget属性,该属性返回一个处理事件的结点的引用。脚本中的决策树需要考虑这个属性,增加代码之后的toggleHighlight()函数如下所示:

  function toggleHighlight(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) if (evt) { var elem if (evt.target) { if (evt.currentTarget && (evt.currentTarget != evt.target)) { elem = evt.currentTarget } else { elem = evt.target } } else { elem = evt.srcElement } elem.className = (evt.type == “mouseover”) ? “highlight” : “normal” } }

  另一个可选的方法是考察由target属性返回的对象的ronodeType属性。一个能够把事件定向给文本结点的浏览器,也可以把一个文本结点的nodeType属性值报告为3,而不是报告为元素结点的类型(其值为1)。如果事件的目标是一个文本结点,则脚本程序就可以通过该文本结点的parentNode属性来得到其上级元素结点的引用。这种方法的决策树在某种程度上得到更多的改进:

  function toggleHighlight(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) if (evt) { var elem if (evt.target) { elem = (evt.target.nodeType == 3) ? evt.target.parentNode : evt.target } else { elem = evt.srcElement } elem.className = (evt.type == “mouseover”) ? “highlight” : “normal” } }

  如果您正在用遵循W3的浏览器阅读本文,则请尝试这个修改过的版本,看看鼠标滚动时的风格变化。

  这个页面使用了嵌入到事件实例中的最新版本的toggleHighlight()函数,展示了如何使用JavaScript为那些能够显示期望效果的浏览器增加额外的价值,同时也可以那些基本的内容提供给仍然使用着较老版本或者不支持脚本编程的浏览器的用户,只不过在模式上不那么动人和便于交互。

  一个事件处理函数的模板

  并不是每个事件处理函数都处理页面元素对象中同样的属性或者行为,但是,从上文的讨论可以派生出来的一个模板,您可以在这个模板的帮助下开始编码。模板如下:

  function functionName(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) if (evt) { var elem if (evt.target) { elem = (evt.target.nodeType == 3) ? evt.target.parentNode : evt.target } else { elem = evt.srcElement } if (elem) { // process event here } } }

  请把第一行的函数名替换为您希望的函数名,并在注视指示的地方开始书写具体事件的代码。这个格式应该可以为您提供一个起点,适合于您采用的任何跨浏览器的事件绑定风格。如果您需要在一个页面中多次使用这个格式,则可以进一步精简代码,即把读取目标的代码抽象成一个可重用的工具函数,然后在每一个事件处理函数中进行调用:

  // shared function function getTargetElement(evt) { var elem if (evt.target) { elem = (evt.target.nodeType == 3) ? evt.target.parentNode : evt.target } else { elem = evt.srcElement } return elem } function functionName(evt) { evt = (evt) ? evt : ((window.event) ? window.event : “”) if (evt) { var elem = getTargetElement(evt) if (elem) { // process event here } } }

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐

  • AWS MEAN堆栈+JavaScript=快速搭建应用

    开发人员在构建Web应用时有许多选择。市面上有无数的框架和语言可选,而像AWS这样的云平台可以方便地部署和扩展应用程序。

  • JDK 8u40更新:新增功能抢先看

    俗话说长江后浪推前浪,一代新人换旧人,Java更新版本交替,也是这样一个道理。甲骨文又给Java添加了哪些新功能。

  • 移动浏览器到云:JavaScript地位正在扩张

    不难发现人们非常喜欢在前端开发中使用JavaScript。但是,令我们惊讶的是后端开发也如此青睐JavaScript,促进了基于云和基于数据中心的托管应用的发展。

  • 移动HTML5挑战何在?

    当HTML5出现时,许多开发者和应用架构师视之为创建平台独立应用、简化你的设备支持以及当新的移动设备OS版本发布时减少应用相关问题的机会。