第16章 事件处理

第16章 事件处理

JavaScript与用户之间的交互是通过事件驱动来实现的,事件驱动是面向对象程序设计的重要概念,其核心就是“以消息为基础,以事件来驱动(message based,event driven)”。当网页对象发生特定事件时,浏览器会自动生成一个事件对象(Event),事件对象通常会沿着DOM节点进行传播,直到被脚本捕获。如果为事件绑定响应程序(事件处理函数),浏览器就会调用该事件处理函数,执行其中的代码,完成预定的任务。本章将详细讲解JavaScript事件处理的基础知识和设计技巧,帮助用户灵活使用JavaScript事件开发各种复杂的Web应用。

【学习重点】

▲ 了解JavaScript事件处理模型

▲ 熟悉基本事件模型

▲ 掌握标准事件模型

▲ 了解IE事件模型

▲ 掌握键盘事件开发

▲ 掌握鼠标事件开发

▲ 掌握页面事件开发

16.1 事件概述

事件(Event)最早是在IE 3.0和Netscape 3.0浏览器中出现的,最初在页面中应用事件处理技术的意图仅是为了解决带宽问题。因为早期的网速是非常慢的,为了解决漫长等待,开发人员只好把服务器端处理的任务部分前移到客户端,让JavaScript来解决。

例如,对用户输入的表单信息进行验证等,于是就发明了多种响应用户行为的事件,如表单提交事件、文本输入时键盘事件、文本框中文本发生变化触发的事件、选择下拉菜单时引发的事件等。因此,早期的事件都集中在使用表单和表单元素上,以便对用户提交数据进行实时验证。

事件(Event)属于DOM的一部分,在DOM 1.0时还没有被定义,DOM 2.0开始定义部分事件,直到2004年发布DOM 3.0时,W3C才完善事件处理模型。

注意:浏览器对DOM 2.0和DOM 3.0的支持不尽完善,特别是低版本浏览器(如IE 7及其以下版本)存在很多问题。即便是现代主流浏览器,用户在使用DOM 3.0部分特性时也需要考虑浏览器兼容性问题。

16.1.1 事件模型分类

JavaScript是基于事件处理的程序,但在早期浏览器中并没有统一的标准,各浏览器厂商分别开发自己的事件处理模型。后来W3C统一了事件处理的技术标准,于是在这个过程中,先后存在4套事件处理模型。

☑ 基本事件模型:通过简单的事件属性,为指定标签绑定事件处理函数。由于这种模型使用比较广泛,且获得了所有浏览器的完全支持,应用比较普及。但该模型功能比较弱,对标签依赖严重,不利于独立开发,面对复杂的Web应用开发会束手无策,因此不建议使用。

☑ 标准事件模型:标准事件模型由W3C制订,包括DOM 1.0、DOM 2.0和DOM 3.0 3个不同版本。目前大部分浏览器都能够支持DOM 2.0,IE部分支持DOM 2.0。但现代浏览器对DOM 3.0支持还不统一、不完善。

☑ IE事件模型:由IE 4.0及其以上版本浏览器支持,它具有标准事件的高级特性,但是用法略有不同。考虑到IE浏览器的市场占有率,用户在设计事件处理时,必须考虑IE的事件处理模型。

☑ Netscape事件模型:由Netscape 4浏览器实现,在Netscape 6中停止支持。

16.1.2 事件流

在页面中可能会存在多个对象同时响应一个事件的现象。那么哪个对象先响应事件,哪个对象后响应事件?

事件流就是针对此类问题而设计的解决方案。简单地说,事件流就是事件发生的先后顺序。

不同事件处理模型对事件流的处理不同,目前主要包括3种事件流类型,简单说明如下。

1.冒泡型事件流

冒泡型事件流的基本思路:事件按照从最特定的事件目标到最不特定的事件目标(Document对象)的顺序触发。简单描述,就是事件从下向上传递,这个传递过程犹如水泡一样不断上升到顶端,被形象地比喻为冒泡。

【示例1】在本示例中,为5层嵌套的div元素定义click事件,同时为每层<div>标签定义类名,当单击<div>标签时,设计当前对象边框显示为红色虚线,同时捕获当前标签的class,以此标识每个标签的响应顺序。

在浏览器中预览当前页面,如果单击最里层的<div>标签,则click事件按着从里到外的顺序逐层响应,最后显示为红色虚线。在<p>标签中显示5层<div>标签的响应顺序,演示效果如图16-1所示。

图16-1 冒泡型事件流演示效果

提示:下面列举几个早期版本浏览器的冒泡顺序和顶端元素。

☑ IE 5.5及其以下版本:div→body→document。

☑ IE 6及其以上版本:div→body→html→document(html元素也可以接收冒泡的事件)。

☑ Mozilla 1.0(Firefox 1.0):div→body→html→document→window(非标准)。

2.捕获型事件流

捕获型事件流与冒泡型事件流正好相反,事件总是从最不精确的对象(document对象)开始触发,然后到最精确。简单地说,事件都是从上往下按顺序响应,即从document到div的顺序响应用户的操作。

【示例2】针对示例1,修改JavaScript脚本,使用addEventListener()方法为5层嵌套的div元素注册click事件,在注册事件时定义响应类型为捕获型事件,即设置方法第三个参数值为true。

在浏览器中预览当前页面,如果单击最里层的<div>标签,则click事件将按着从外到里的顺序逐层响应,在<p>标签中显示5层<div>标签的响应顺序,如图16-2所示。

图16-2 捕获型事件流演示效果

3.标准事件流

W3C定义的标准事件处理模型能同时支持捕获型事件流和冒泡型事件流两种类型,但是捕获型事件流先发生,然后才发生冒泡型事件流。两种事件流会触及DOM中的所有层级对象,从document对象开始,最后返回document对象结束。

16.1.3 事件类型

根据触发事件的事物和事件发生的对象,可以将浏览器中发生的事件分成几个类型。DOM标准定义了以下几组事件。

☑ 鼠标事件:与鼠标操作相关的各种行为,它可以细分为两类:跟踪鼠标当前定位(如mouseover、mouseout)的事件和跟踪鼠标单击(如mouseup、mousedown、click)的事件。

☑ 键盘事件:与键盘操作相关的各种行为,包括追踪键盘敲击和其上下文,追踪键盘包括3种类型:keyup、keydown和keypress。

☑ 页面事件:关于页面本身的行为,如当首次载入页面时触发load事件和离开页面时触发unload和beforeunload事件。此外,JavaScript的错误使用错误事件追踪,可以让用户独立地处理错误。

☑ UI事件:追踪用户在页面中的各种行为,如监听用户在表单中输入,可以通过focus(获得焦点)和blur(失去焦点)两个事件。

☑ 表单事件:与表单交互的各种行为,如submit事件用来追踪表单的提交,change事件监听用户在文本框中的输入,而select事件可以监听下拉菜单发生更新等。

16.2 基本事件模型

浏览器都支持基本事件处理模型,这种事件处理方式被广泛应用到网页交互设计中,本节将详细讲解基本事件处理的一般方式。

16.2.1 基本事件类型

基本事件处理模型的形式很简单,主要通过事件属性来设计。HTML规定每种元素支持不同的事件类型,详细说明如表16-1所示。

表16-1 基本事件类型列表

16.2.2 绑定事件

在基本事件模型中,JavaScript支持两种绑定方式:

☑ 静态绑定

把JavaScript脚本作为属性值直接赋予给事件属性。

【示例1】在本示例中,把JavaScript脚本以字符串的形式传递给onclick属性,为<button>标签绑定click事件。当单击按钮时,就会触发click事件,执行这行JavaScript脚本。

     <!doctype html>
     <html>
     <head>
     <meta charset="utf-8">
     </head>
     <body>
     <button οnclick="alert('你单击了一次!');">按钮</button>
     </body>
     </html>

☑ 动态绑定

利用DOM对象的事件属性进行赋值。

【示例2】在本示例中,JavaScript借助DOM方法(document.getElementById)匹配到button元素,然后把一个匿名函数作为值传递给button元素的onclick属性,实现事件绑定操作。

这种方法可以在脚本中直接为页面元素附加事件,不用破坏HTML结构,比上一种方式更加灵活。

16.2.3 事件处理函数的返回值

事件处理是一种异步响应行为,事件处理函数从来不会明确要求有返回值,如果不返回值,就会发生默认的动作。因此事件处理函数的返回值一般没有实际价值。不过,用户可以利用事件处理函数返回值来设置下一步的操作,如执行或禁止默认操作。

【示例】在本示例中,为form元素的onsubmit事件属性定义字符串脚本,设计当文本框中输入值为空时,定义事件处理函数返回值为false。由于该返回值为false,将强制表单禁止提交数据。

在上面字符串脚本中,this表示当前form元素,elements[0]表示姓名文本框,通过该文本框的value属性值length长度为0,表示当前文本框为空。

16.2.4 事件处理函数的参数

事件处理函数是一种特殊的函数,一般不会传递函数参数。在DOM事件模型中,事件处理函数默认会传递一个event实参,这是一个Event对象。当事件发生时,事件消息就会通过Event对象进行传播。

【示例】在本示例中,为按钮对象绑定一个事件处理函数。在这个事件处理函数中,参数e为形参,响应事件之后,浏览器会把Event对象传递给参数变量e,作为一个实参传递给函数体。然后,读取Event对象包含的事件信息,以便获取当前事件包含的复杂信息,最后输出当前源对象节点名称,显示效果如图16-3所示。

图16-3 捕获当前事件源

提示:在上面脚本中,为了能够兼容IE事件模型和标准事件模型,分别使用一个逻辑运算符和一个条件运算符来匹配不同的模型。

IE事件模型和标准事件模型对于Event对象的处理方式不同:IE把Event对象作为Window对象的一个属性来定义,而标准模型把Event作为一个默认参数来定义。所以,在处理event参数时,应该判断event在当前解析环境中的状态,如果当前浏览器支持,则使用event(标准模型);如果不支持,则说明当前环境是IE浏览器,通过window.event获取event。event.srcElement表示当前事件的源,即响应事件的当前对象,这是IE模型用法。但是标准模型不支持该属性,需要使用Event对象的target属性,它是一个符合标准的源属性。为了能够兼容不同的浏览器,这里使用了一个条件运算符,先判断event.srcElement属性是否存在,否则使用event.target属性来获取当前事件对象的源。

16.2.5 事件处理函数中的this

在简单事件模型中,this表示当前事件对象,与Event对象的srcElement属性(IE模型)或者target(标准模型)属性所代表的意思相同。

【示例1】在本示例中,定义当单击按钮时改变当前按钮的背景色为红色,其中this关键字就表示button按钮对象。

也可以使用下面一行代码来表示:

在一些特殊的环境中this并非都表示当前事件对象。

【示例2】在本示例中,分别使用this和事件源来指定当前对象,但是会发现this并没有指向当前的事件对象按钮,而是指向Window对象,所以这时继续使用this引用当前对象就错了。

为了能够准确获取当前事件对象,在第二个按钮的click事件处理函数中,直接把event传递给btn2()。如果不传递该参数,支持标准模型的浏览器就会找不到Event对象。

16.3 标准事件模型

标准事件模型是W3C制定并推荐的一种高级事件处理方法。与基本事件模型相比,标准事件模型功能更强大,支持各种流行、复杂的事件处理技巧。

16.3.1 事件传播

根据事件流类型,可以把事件传播的整个过程分为3个阶段。

☑ 捕获阶段:事件从Document对象沿着文档树向下传播到目标节点,如果目标节点的任何一个上级节点注册了相同事件的处理函数,那么事件在传播的过程中就会首先在最靠近顶部的上级节点执行,依次向下传播。

☑ 目标阶段:注册在目标节点上的事件处理函数被执行。

☑ 冒泡阶段:事件从目标节点向上触发,如果上级节点注册了相同的事件,将会逐级响应,依次向上传播。

在整个事件传播过程中,可以使用Event对象的stopPropagating()方法停止事件传播,也就是让其他元素对当前事件不可见。在IE中,通过设置cancelBubble为true实现相同的目标。

有些事件会引发标签的默认动作。例如,在单击<a>标签时,默认动作跳转到指定的URL。默认动作只有在事件传播的3个阶段都完成之后才会执行,通过调用Event对象的preventDefault()方法可以阻止默认动作发生。

16.3.2 注册事件

在标准事件模型中,通过调用元素对象的addEventListener()方法来注册事件,与基本模型中的绑定事件操作相同。addEventListener()方法包含3个参数,语法格式如下:

     element.addEventListener(String type, Function listener, boolean useCapture);

参数说明如下。

☑ type:注册事件的类型名。事件类型与事件属性不同,事件类型名没有事件属性名的on前缀。例如,对于事件属性onclick来说,所对应的事件类型为click。

☑ listener:监听函数,即事件处理函数。在指定类型的事件发生时将调用该函数。在调用这个函数时,默认传递给它的唯一参数是Event对象。

☑ useCapture:是一个布尔值。如果为true,则指定的事件处理函数将在事件传播的捕获阶段触发;如果为false,则事件处理函数将在冒泡阶段触发。

【示例】在本示例中,使用addEventListener()方法为所有按钮(button元素)注册一个事件处理函数。在JavaScript脚本中,首先调用document的getElementsByTagName()方法捕获所有按钮对象,然后使用for in语句遍历按钮集(btn),并使用addEventListener()方法分别为每一个按钮注册一个事件函数,该函数获取当前对象所显示的文本。

在浏览器中预览,单击不同的按钮,则浏览器会自动弹出对话框,显示按钮的名称,如图16-4所示。

图16-4 响应注册事件

提示:早期IE浏览器不支持addEventListener()方法。从IE 8开始才完全支持标准事件模型。

【拓展】使用addEventListener()方法能够为多个对象注册相同的事件处理函数,也可以为同一个对象注册多个事件处理函数。为同一个对象注册多个事件处理函数对于模块化开发非常有用。

在下面示例中,为段落文本注册了两个事件处理函数。当鼠标移到段落文本上面时会显示为蓝色背景,而当鼠标移出段落文本时会自动显示为红色背景。这样就不需要破坏文档结构为段落文本增加多个事件属性。

16.3.3 注销事件

使用removeEventListener()方法可以从指定对象中删除已经注册的事件处理函数。

     element.removeEventListener(String type, Function listener, boolean useCapture);

removeEventListener()方法包含3个参数,语法格式如下:参数说明参阅16.3.2节addEventListener()方法参数说明。

【示例】在本示例中,分别为按钮a和按钮b注册click事件,其中按钮a的事件函数为ok(),按钮b的事件函数为delete_event()。在浏览器中预览,当单击“点我”按钮将弹出一个对话框,在不删除之前这个事件是一直存在的。当单击“删除事件”按钮之后,“点我”按钮将失去任何效果,演示效果如图16-5所示。

图16-5 注销事件

提示:removeEventListener()方法只能够删除addEventListener()方法注册的事件。如果直接使用onclick等直接写在元素上的事件,将无法使用removeEventListener()方法删除。

当临时注册一个事件处理函数时,可以在处理完毕之后迅速删除它,这样能够节省系统资源。

16.3.4 事件类型

在标准事件模型中,事件模块又包含多个子模块,每个子模块提供对某类事件的支持。例如,MouseEvent子模块提供了对mousedown、mouseup、mouseover、mouseout和click事件类型的支持。标准事件模型定义了4个子模块。

☑ HTMLEvents:接口为Event,支持的事件类型包括abort、blur、change、error、focus、load、resize、scroll、select、submit、unload。

☑ MouseEvents:接口为MouseEvent,支持的事件类型包括click、mousedown、mousemove、mouseout、mouseover、mouseup。

☑ UIEvents:接口为UIEvent,具体支持事件类型可以参阅表16-2所示。

☑ MutationEvents:接口为MutationEvent,具体支持事件类型可以参阅表16-2所示。

在标准事件模型中,事件类型说明如表16-2所示。HTMLEvents和MouseEvents模块定义的事件类型与基础事件模型中的事件类型相似,UIEvents模块定义的事件类型与HTML表单元素的支持的获得焦点、失去焦点和单击事件功能类似,MutationEvents模块定义的事件是在文档改变时生成的,一般不常用。

表16-2 标准事件类型

表16-2详细说明了每一种事件类型的相关信息,其中冒泡列定义该事件类型是否在事件传播过程中向文档上层冒泡,Y表示冒泡,而N表示不冒泡。默认动作列定义了事件类型是否具有使用preventDefault()方法取消事件的默认动作,Y表示支持该方法,而N表示不支持该方法。

16.3.5 Event对象

Event是标准事件模型的基础,所有事件类型都继承Event,因此所有事件对象都拥有Event属性和方法。简单说明如下。

☑ type:获取事件的类型,如click、mouseover等。

☑ target:发生事件的节点。一般利用该属性来获取当前被激活事件的具体对象。

☑ currentTarget:事件当前传播到的节点。如果在传播过程的捕获阶段或冒泡阶段处理事件,这个属性的值就与target属性值不同。

☑ eventPhase:显示当前所处的事件传播过程的阶段。

☑ timeStamp:事件发生的时间。

☑ bubbles:显示事件是否能够在文档中冒泡。

☑ cancelable:显示事件是否能够取消默认动作。

☑ stopPropagation():阻止当前事件从正在处理它的节点传播。

☑ preventDefault():阻止默认动作的执行。

上述属性都是只读属性,具体说明和用法可以参阅DOM参考手册。

除了Event提供基本属性和方法外,各个事件子模块也都定义了私有属性和方法。例如,UIEvent提供了view(发生事件的Window对象)和detail(事件的详细信息)属性。而MouseEvent除了拥有Event和UIEvent属性和方法外,也定义了几个比较实用的属性。

☑ button:当在声明mousedown、mouseup和click事件时,显示鼠标键的状态值。0表示左键,1表示中间键,2表示右键。

☑ altKey、ctrlKey、metaKey和ShiftKey:表示在声明鼠标事件时,是否按下了Alt键、Ctrl键、Meta键和Shift键。

☑ clientX、clientY:声明事件发生时鼠标指针相对于客户区域浏览器窗口左上角的x轴和y轴坐标值。

☑ screenX、screenY:声明事件发生时鼠标指针相对于用户显示器左上角的x轴和y轴坐标值。

☑ relatedTarget:引用与事件的目标节点相关的节点。对于mouseover事件来说,它是鼠标移到目标上时所离开的那个节点;对于mouseout事件来说,它是离开目标时鼠标将要进入的那个节点。

16.4 IE事件模型

IE事件模型与标准事件模型相近,但用法略微不同,如IE事件模型把Event对象视为Window对象的一个属性,IE事件模型的事件传播包括两个阶段:目标事件和冒泡。下面介绍IE事件模型的基本使用。

16.4.1 注册和销毁事件

IE事件模型使用attachEvent()和detachEvent()方法注册和注销事件,这与标准事件模型中的addEventListener()和removeEventListener()方法非常相似,但是attachEvent()和detachEvent()方法不支持事件捕获,且它们的参数只有两个。具体用法如下:

     element.attachEvent(etype,eventName)
     element.detachEvent(etype,eventName)

参数说明如下。

☑ etype:设置事件类型,如onclick、onkeyup、onmousemove等。

☑ eventName:设置事件名称,也就是事件处理函数。

【示例1】在本示例中,为段落标签<p>注册事件,设计当鼠标经过时,段落文本背景色显示为蓝色;当鼠标移开之后,背景色显示为红色。

提示:使用attachEvent()注册事件时,其事件处理函数的调用对象不再是当前事件对象本身,而是Window对象,因此事件函数中的this就指向window,而不是当前源对象,如果要获取当前源对象,应该使用Event的srcElement属性。

IE事件模型中的事件类型名称需要加上on前缀。而使用addEventListener()方法时,不需要这个on前缀,如click。

【示例2】针对上面示例,修改JavaScript脚本,设计段落标签<p>仅响应一次鼠标交互行为。当第二个鼠标经过和移出段落文本时,所注册的事件不再有效。

【拓展】由于早期IE不支持标准事件模型,为了保证页面的兼容性,开发时需要兼容两种事件模型以实现在不同浏览器中具有相同的交互行为。

【示例3】在本示例中,使用if语句判断当前浏览器支持的事件处理模型,然后分别使用标准注册方法和IE注册方法为段落文本注册mouseover和mouseout两个事件。当触发mouseout事件之后,再把mouseover和mouseout事件注销掉。

16.4.2 Event对象

在IE事件模型中,Event被定义为Window对象的子对象,通过window.event进行访问。该对象所提供的属性与标准事件模型中的Event有很多相似。详细说明如下。

☑ type:获取事件的类型,等同于标准中的type属性。

☑ srcElement:发生事件的元素。等同于标准中的target属性。

☑ button:表示按下鼠标键的状态,1表示左键,2表示右键,4表示中间键。可以组合使用,如1+2=3,则3表示同时按下左右键。这与标准事件模型中的button属性功能相同,但是属性值的含义不同。

☑ clientX、clientY:声明事件发生时鼠标指针相对于客户区域浏览器窗口左上角的x轴和y轴坐标值。等同于标准中的同名属性。

☑ altKey、ctrlKey和ShiftKey:表示在声明鼠标事件时,是否按下了Alt键、Ctrl键和Shift键。等同于标准中的同名属性,但是没有metaKey属性。

☑ offsetX、offsetY:声明事件发生时鼠标指针相对于源元素左上角的x轴和y轴坐标值。标准事件模型中没有提供相同功能的属性。

☑ keyCode:声明keydown和keyup事件的键代码以及keypress事件的Unicode字符。可以借助String.fromCharCode()方法将这些编码转换为字符。

☑ fromElement和toElement:分别声明鼠标移动过的元素和鼠标将要移动到的元素。等同于标准事件模型中的relatedTarget属性。

以上属性为只读属性,下面两个属性可以进行设置。

☑ cancelBubble:为true可以阻止事件冒泡,默认为false。等同于标准事件模型中的stopPropagation()方法。

☑ returnValue:为false可以阻止事件的默认动作,默认为true。等同于标准事件模型中的preventDefault()方法。

提示:标准事件模型中的Event与IE事件模型中的Event有很多相似之处,但是IE事件模型通过全局Window对象的event属性来访问Event对象,这样IE就可以把所有事件信息都保存到全局变量中。

为了能够兼容IE和标准两种不同的事件模型,可以使用下面表达式来进行兼容。

     var event=event || window.event;  //兼容不同模型的Event对象

上面代码右侧是一个选择运算表达式,如果事件处理函数存在event实参,则使用event形参来传递事件信息;如果不存在event参数,则调用Window对象的event属性来获取事件信息。把上面表达式放在事件处理函数中即可进行兼容。

在以事件驱动为核心的设计模型中,一次只能够处理一个事件,由于从来不会并发两个事件,因此使用全局变量来存储事件信息是一种比较安全的方法。

IE事件模型中的冒泡与标准事件模型中的冒泡区别就在于冒泡的方式不同,IE没有stopProgressive()方法,但是可以使用Event对象的cancelBubble属性进行设置:

     window.event.cancelBubble=true;

该属性设置只能够适用当前事件,新生的事件将被赋予默认的false。

16.5 鼠标事件开发

鼠标事件是Web开发中最常用的事件类型,鼠标事件类型详细说明如表16-3所示。

表16-3 鼠标事件类型

【示例】在本示例中,定义在段落文本范围内侦测鼠标的各种动作,并在文本框中实时显示各种事件的类型,以提示当前的用户行为。

16.5.1 鼠标点击

鼠标点击事件包括4个:click(单击)、dblclick(双击)、mousedown(按下)和mouseup(松开)。其中click事件类型比较常用,而mousedown和mouseup事件类型多用在鼠标拖放、拉伸操作中。当这些事件处理函数的返回值为false时,则会禁止绑定对象的默认行为。

【示例】在本示例中,当定义超链接指向自身时(在设计过程中href属性值暂时使用“#”或“?”表示),可以取消超链接被单击时默认行为,即刷新页面。

当单击示例中的超链接时,页面不会发生跳转变化(即禁止页面发生刷新效果)。

16.5.2 案例:鼠标移动

mousemove事件类型是一个实时响应的事件,当鼠标指针的位置发生变化时(至少移动1个像素),就会触发mousemove事件。该事件响应的灵敏度主要参考鼠标指针移动速度的快慢,以及浏览器跟踪更新的速度。

【示例】本示例演示了如何综合应用各种鼠标事件实现页面元素拖放操作的设计过程。实现拖放操作设计,需要理清和解决以下几个问题。

☑ 定义拖放元素为绝对定位,以及设计事件的响应过程。这个比较容易实现。

☑ 清楚几个坐标概念:按下鼠标时的指针坐标,移动中当前鼠标的指针坐标,松开鼠标时的指针坐标,拖放元素的原始坐标,拖动中的元素坐标。

☑ 算法设计:按下鼠标时,获取被拖放元素和鼠标指针的位置,在移动中实时计算鼠标偏移的距离,并利用该偏移距离加上被拖放元素的原坐标位置,获得拖放元素的实时坐标。

如图16-6所示,其中变量ox和oy分别记录按下鼠标时被拖放元素的纵横坐标值,它们可以通过事件对象的offsetLeft和offsetTop属性获取。变量mx和my分别表示按下鼠标时,鼠标指针的坐标位置。而event.mx和event.my是事件对象的自定义属性,用它们来存储当鼠标移动时鼠标指针的实时位置。

图16-6 拖放操作设计示意图

当获取了上面3对坐标值之后,就可以动态计算拖动中元素的实时坐标位置,即x轴值为ox+event.mx–mx,y轴为oy+event.my–my。当释放鼠标按钮时,则可以释放事件类型,并记下松开鼠标指针时拖动元素的坐标值,以及鼠标指针的位置,留待下一次拖放操作时调用。

整个拖放操作的示例代码如下:

16.5.3 案例:鼠标经过

鼠标经过包括移过和移出两种事件类型。当移动鼠标指针到某个元素上时,将触发mouseover事件;而当把鼠标指针移出某个元素时,将触发mouseout事件。如果从父元素中移到子元素中时,也会触发父元素的mouseover事件类型。

【示例】在本示例中分别为3个嵌套的div元素定义了mouseover和mouseout事件处理函数,这样当从外层的父元素中移到内部的子元素中,将会触发父元素的mouseover事件类型,但是不会触发mouseout事件类型。

16.5.4 案例:鼠标来源

当一个事件发生后,可以使用事件对象的target属性获取发生事件的节点元素。如果在IE浏览器中实现相同的目标,可以使用srcElement属性。

【示例1】在本示例中当鼠标移过页面中的div元素时,会弹出提示对话框,提示当前元素的节点名称。

另外,在DOM事件模型中,还定义了currentTarget属性,当事件在传播过程中(如捕获和冒泡阶段)时,该属性值与target属性值不同。因此,一般在事件处理函数中,应该使用该属性而不是this关键词获取当前对象。

除了使用上面提到的通用事件属性外,如果想获取鼠标指针来移动哪个元素,在DOM事件模型中,可以使用relatedTarget属性获取当前事件对象的相关节点元素;而在IE事件模型中,可以使用fromElement获取mouseover事件中鼠标移到过的元素,使用toElement属性获取在mouseout事件中鼠标移到的文档元素。

【示例2】在本示例中,当鼠标移到div元素上时,会弹出“BODY”字符提示信息,说明鼠标指针是从body元素过来的;而移开鼠标指针时,又弹出“BODY”字符提示信息,说明离开div元素将要移到的元素。

16.5.5 案例:鼠标定位

当事件发生时,获取鼠标的位置是很重要的事件。由于浏览器的不兼容性,不同浏览器分别在各自事件对象中定义了不同的属性,说明如表16-4所示。这些属性都以像素值定义了鼠标指针的坐标,但是它们参照的坐标系不同,导致准确计算鼠标的位置比较麻烦。

表16-4 属性及其兼容属性

【示例1】本示例介绍如何配合使用多种鼠标坐标属性,以实现兼容不同浏览器的鼠标定位设计方案。

首先,来看看screenX和screenY属性。这两个属性获得了所有浏览器的支持,应该说是最优选用属性,但它们的坐标系是计算机屏幕,也就是说,以计算机屏幕左上角为定位原点。这对于以浏览器窗口为活动空间的网页来说,没有任何价值。因为不同的屏幕分辨率,不同的浏览器窗口大小和位置都使在网页中定位鼠标成为一件很困难的事情。

其次,如果以Document对象为坐标系,则可以考虑选用pageX和pageY属性,实现在浏览器窗口中进行定位。这对于设计鼠标跟随是一个好主意,因为跟随元素一般都以绝对定位的方式在浏览器窗口中移动,在mousemove事件处理函数中把pageX和pageY属性值传递给绝对定位元素的top和left样式属性即可。

IE浏览器不支持上面属性,为此还需寻求兼容IE浏览器的方法。再看看clientX和clientY属性是以Window对象为坐标系,且IE浏览器支持它们,可以选用它们。不过考虑Window等对象可能出现的滚动条偏移量,所以还应加上相对于Window对象的页面滚动的偏移量。

设计代码如下:

在上面代码中,先检测pageX和pageY属性是否存在,如果存在则获取它们的值;如果不存在,则检测并获取clientX和clientY属性值,然后加上document.documentElement和document.body.对象的scrollLeft和scrollTop属性值,这样就可以在不同浏览器中获得相同的坐标值。

【示例2】封装鼠标定位代码。设计思路:能够根据传递的具体对象,以及相对鼠标指针的偏移值,即命令该对象能够跟随鼠标移动。

先定义一个封装函数,设计函数传入参数为对象引用指针、相对鼠标指针的偏移距离,以及事件对象。然后封装函数能够根据事件对象获取鼠标的坐标值,并设置该对象为绝对定位,绝对定位的值为鼠标指针当前的坐标值。

封装代码如下:

下面测试封装代码,为Document对象注册鼠标移动事件处理函数,并传入鼠标定位封装函数,传入的对象为<div>元素,设置其位置向鼠标指针右下方偏移(10,20)的距离。考虑到非IE浏览器通过参数形式传递事件对象,所以不要忘记在调用函数中传递事件对象。

【示例3】获取鼠标指针在元素内的坐标,即以元素自身为坐标参照物来获取鼠标指针的位置。

使用offsetX和offsetY属性可以实现这样的目标,但是Mozilla浏览器不支持。不过可以选用layerX和layerY属性来兼容Mozilla浏览器。

设计代码如下:

但是,layerX和layerY属性是以绝对定位的父元素为参照物的,而不是元素自身。如果没有绝对定位的父元素,则会以Document对象为参照物。为此,可以通过脚本动态添加或者手动添加的方式,设计在元素的外层包围一个绝对定位的父元素,这样可以解决浏览器兼容问题。考虑到元素之间的距离所造成的误差,可以适当减去一个或几个像素的偏移量。

完整设计代码如下:

这种做法能够解决在元素内部定位鼠标指针的问题。但是由于在元素外面包裹了一个绝对定位的元素,会破坏整个页面的结构布局。在确保这种人为方式不会导致结构布局混乱的前提下,可以考虑选用这种方法。

16.5.6 鼠标按键

通过事件对象的button属性可以获取当前鼠标按下的键,该属性可用于click、mousedown、mouseup事件类型。不过不同模型的约定不同,具体说明如表16-5所示。

表16-5 鼠标事件对象的button属性

IE浏览器支持位掩码技术,它能够侦测到同时按下的多个键。例如,当同时按下左右键,则button属性值为1+2=3;同时按下中键和右键,则button属性值为2+4=6;同时按下左键和中键,则button属性值为1+4=5,同时按下3个键,则button属性值为1+2+4=7。

但是DOM 2模型不支持这种掩码技术,如果同时按下多个键,就不能够准确侦测。例如,按下右键(2)与按下左键和右键(0+2=2)的值是相同的。因此,对于DOM 2模型来说,这种button属性约定值存在很大的缺陷。不过,在实际开发中很少需要同时检测多个鼠标按钮问题,也许仅需要探测鼠标左键或右键点击行为。

【示例】下面代码能够监测右键单击操作,并阻止发生默认行为。

【拓展】当鼠标点击事件发生时,会触发很多事件:mousedown、mouseup、click、dblclick。这些事件响应的顺序如下:

     mousedown →mouseup→click→mousedown→mouseup→click→dblclick

当鼠标在对象间移动时,首先触发的事件是mouseout,即在鼠标移出某个对象时发生。接着,在这两个对象上都会触发mousemove事件。最后,在鼠标进入的对象上触发mouseover事件。

16.6 键盘事件开发

当用户操作键盘时会触发键盘事件,键盘事件主要包括下面3种类型。

☑ keydown:在按下某个键时触发。如果按住某个键,会不断触发该事件,但是Opera浏览器不支持这种连续操作。该事件处理函数返回false时,会取消默认的动作(如输入的键盘字符,在IE和Safari浏览器下还会禁止keypress事件响应)。

☑ keypress:按下某个键盘键并释放时触发。如果按住某个键,会不断触发该事件。该事件处理函数返回false时,会取消默认的动作(如输入的键盘字符)。

☑ keyup:释放某个键盘键时触发。该事件仅在松开键盘时触发一次,不是一个持续的响应状态。

当获知用户正按下的键码时,可以使用keydown、keypress和keyup事件获取这些信息。其中keydown和keypress事件基本上是同义事件,它们的表现也完全一致,不过一些浏览器不允许使用keypress事件获取按键信息。虽然所有元素都支持键盘事件,但键盘事件多被应用在表单输入中。

【示例】在本示例中,可以实时捕获键盘操作的各种细节,即键盘响应事件类型及对应的键值。

16.6.1 认识键盘事件的属性

键盘事件定义了很多属性,如表16-6所示。利用这些属性可以精确控制键盘操作。键盘事件属性一般只在键盘相关事件发生时才会存在于事件对象中,但是ctrlKey和shiftKey属性除外,因为它们可以在鼠标事件中存在。例如,当按下Ctrl或Shift键时单击鼠标操作。

表16-6 键盘事件定义的属性

【示例1】ctrlKey和shiftKey属性可存在于键盘和鼠标事件中,表示键盘上的Ctrl和Shift键是否被按住。本示例能够监测Ctrl和Shift键是否被同时按下。如果同时按下,且鼠标单击某个页面元素,则会把该元素从页面中删除。

keyCode和charCode属性使用比较复杂,但是它们在实际开发中又比较常用,故比较这两个属性在不同事件类型和不同浏览器中的表现是非常必要的,如表16-7所示。读者可以根据需要有针对性地选用事件响应类型和引用属性值。

表16-7 keyCode和charCode属性值

某些键的可用性不是很确定,如PageUp和Home键等。不过常用功能键和字符键都是比较稳定的,如表16-8所示。

表16-8 键位和码值对照表

【示例2】本示例演示了如何使用方向键控制页面元素的移动效果。

在上面示例中,首先获取页面元素,然后通过CSS脚本控制元素绝对定位、大小和背景色。然后在Document对象上注册鼠标按下事件类型处理函数,在事件回调函数keyDown()中侦测当前按下的方向键,并决定定位元素在窗口中的位置。其中元素的offsetLeft和offsetTop属性可以存取它在页面中的位置。

16.6.2 键盘事件响应顺序

当按下键盘键时,会连续触发多个事件,它们将按顺序发生。

☑ 对于字符键来说,键盘事件的响应顺序如下:

▶ keydown

▶ keypress

▶ keyup

☑ 对于非字符键(如功能键或特殊键)来说,键盘事件的响应顺序如下:

▶ keydown

▶ keyup

☑ 如果按下字符键不放,则keydown和keypress事件将逐个持续发生,直至松开按键。

☑ 如果按下非字符键不放,则只有keydown事件持续发生,直至松开按键。

【示例】下面设计一个简单示例,以获取键盘事件响应顺序,如图16-7所示。

图16-7 键盘事件响应顺序比较效果

16.7 页面事件开发

所有页面事件都明确地处理整个页面的函数和状态。主要包括页面的加载和卸载,即用户访问页面和离开关闭页面的事件类型。

16.7.1 页面初始化

load事件类型在页面完全加载完毕时触发。该事件包含所有的图形图像、外部文件(如CSS、JavaScript文件等)的加载,也就是说,在页面所有内容全部加载之前,任何DOM操作都不会发生。为Window对象绑定load事件类型的方法有两种。

☑ 直接为Window对象注册页面初始化事件处理函数:

☑ 在页面<body>标签中定义onload事件处理属性:

【示例1】如果同时使用上面两种方法定义页面初始化事件类型,它们并没有发生冲突,也不会出现两次触发事件。

原来JavaScript解释器在编译时,如果发现同时使用两种方法定义load事件类型,会使用Window对象注册的事件处理函数覆盖掉body元素定义的页面初始化事件属性。

【示例2】在本示例中,函数f2()被调用,而函数f1()就被覆盖掉。

【拓展】在实际开发中,load事件类型经常需要调用附带参数的函数,但是load事件类型不能够直接调用函数,要解决这个问题,可以有两种解决方法。

☑ 在body元素中通过事件属性的形式调用函数:

☑ 通过函数嵌套或闭包函数来实现:

或者闭包函数形式,这样在注册事件时,虽然调用的是函数,但是其返回值依然是一个函数,不会引发语法错误。

通过这种方法,可以实现在load事件类型上绑定更多的响应回调函数。

但是,如果分别绑定load事件处理函数,则就会发生相互覆盖,最终只能够有一个绑定响应函数被调用。

还可以通过事件注册的方式来实现:

16.7.2 结构初始化

在传统事件模型中,load是页面中最早被触发的事件。不过当使用load事件来初始化页面时可能会存在一个问题,就是当页面中包含很大的文件时,load事件需要等到所有图像全部载入完成之后才会被触发。也许用户希望某些脚本能够在页面结构加载完毕之后就能够被执行。这怎么办呢?

这时可以考虑使用DOMContentLoaded事件类型。作为DOM标准事件,它是在DOM文档结构加载完毕时触发的,因此要比load事件类型先被触发。目前,Mozilla和Opera新版本已经支持了该事件,而IE和Safari浏览器还不支持。

【示例1】如果在标准DOM中,可以这样设计。

这样,在图片加载之前,会弹出“我提前执行了”的提示信息,而当图片加载完毕之后才会弹出“页面初始化完毕”提示信息。这说明在页面HTML结构加载完毕之后触发DOMContentLoaded事件类型,也就是说,在文档标签加载完毕触发该事件,并调用函数f(),然后当文档所有内容加载完毕(包括图片下载完毕),才触发load事件类型,并调用函数f1()。

【示例2】由于IE浏览器不支持DOMContentLoaded事件类型,为了实现兼容处理,需要运用一点小技巧,即在文档中写入一个新的script元素,但是该元素会延迟到文件最后加载。然后,使用Script对象的onreadystatechange方法进行类似的readyState检查后及时调用载入事件。

在写入的<script>标签中包含了defer属性,defer表示“延期”的意思,使用defer属性可以让脚本在整个页面装载完成之后再解析,而非边加载边解析。这对于只包含事件触发的脚本来说,可以提高整个页面的加载速度。与src属性联合使用,它还可以使这些脚本在后台被下载,前台的内容则正常显示给用户。目前只有IE浏览器支持该属性。当定义了defer属性后,<script>标签中就不应包含document.write命令,因为document.write将产生直接输出效果,而且不包括任何立即执行脚本要使用的全局变量或者函数。

当<script>标签在文档结构加载完毕之后才加载,于是只要判断它的状态就可以确定当前文档结构是否已经加载完毕,并触发响应事件。

【示例3】针对Safari浏览器,可以使用setInterval()函数周期性地检查Document对象的readyState属性,随时监控文档是否加载完毕,如果完成则调用回调函数:

把上面3段条件结构合并在一起,即可实现兼容不同浏览器的DOMContentLoaded事件处理函数。

16.7.3 页面卸载

unload表示卸载的意思,这个事件在从当前浏览器窗口内移动文档的位置时触发,也就是说,通过超链接、前进或后退按钮等方式从一个页面跳转到其他页面,或者关闭浏览器窗口时触发。

【示例】下面函数的提示信息将在卸载页面时发生,即在离开页面或关闭窗口前执行。

在unload事件类型中无法有效阻止默认行为,因为该事件结束后,页面将不复存在。由于在窗口关闭或离开页面之前只有很短的时间来执行事件处理函数,所以不建议使用该事件类型。使用该事件类型的最佳方式是取消该页面的对象引用。

【拓展】beforeunload事件类型与unload事件类型功能相近,不过它更人性化,如果beforeunload事件处理函数返回字符串信息,那么该字符串会显示一个确认对话框中,询问用户是否离开当前页面。例如,运行下面的示例,当刷新或关闭页面时,会弹出如图16-8所示的提示信息。

图16-8 操作提示对话框

beforeunload事件处理函数返回值可以为任意类型,IE和Safari浏览器的JavaScript解释器能够调用toString()方法把它转换为字符串,并显示在提示对话框中。而对于Mozilla浏览器来说,则会视为空字符串显示。如果beforeunload事件处理函数没有返回值,则不会弹出任何提示对话框,此时与unload事件类型响应效果相同。

16.7.4 窗口重置

resize事件类型是在浏览器窗口被重置时触发的,如当用户调整窗口大小,或者最大化、最小化、恢复窗口大小显示时触发resize事件。利用该事件可以跟踪窗口大小的变化,以便动态调整页面元素的显示大小。

【示例】本示例能够跟踪窗口大小变化,及时调整页面内红色盒子的大小,使其始终保持与窗口固定比例的大小显示。

16.7.5 页面滚动

scroll事件类型用于在浏览器窗口内移动文档的位置时触发,如通过键盘箭头键、翻页键或空格键移动文档位置,或者通过滚动条滚动文档位置。利用该事件可以跟踪文档位置变化,及时调整某些元素的显示位置,确保它始终显示在屏幕可见区域中。

【示例】在本示例中,控制红色小盒子始终位于窗口内坐标为(100px,100px)的位置。

还有一种方法,就是利用settimeout()函数实现每间隔一定时间校正一次元素的位置,不过这种方法的损耗比较大,不建议选用。

16.7.6 错误处理

error事件类型是在JavaScript代码发生错误时触发的,利用该事件可以捕获并处理错误信息。error事件类型与try/catch语句功能相似,都用来捕获页面错误信息。不过error事件类型无须传递事件对象,且可以包含已经发生错误的解释信息。

【示例】在本示例中,当页面发生编译错误时,将会触发error事件注册的事件处理函数,并弹出错误信息。

在error事件处理函数中,默认包含3个参数,其中第一个参数表示错误信息,第二个参数表示出错文件的URL,第三个参数表示文件中错误位置的行号。

error事件处理函数的返回值可以决定浏览器是否显示一个标准出错信息。如果返回值为false,则浏览器会弹出错误提示对话框,显示标准的出错信息;如果返回值为true,则浏览器不会显示标准出错信息。

16.8 UI事件开发

UI是User Interface一词的缩写,表示用户界面。UI事件负责响应用户与浏览器或页面元素的交互,主要包括focus(获取焦点)和blur(失去焦点)事件类型。

1.focus

当单击或使用Tab键切换到某个表单元素或超链接对象时,会触发该事件。focus事件是确定页面内鼠标当前定位的一种方式。在默认情况下,整个文档处于焦点状态,但是单击或者使用Tab键可以改变焦点的位置。

2.blur

blur事件类型表示在元素失去焦点时响应,它与focus事件类型是对应的,主要作用于表单元素和超链接对象。

【示例】在本示例中为所有输入表单元素绑定了focus和blur事件处理函数,设置当元素获取焦点时呈凸起显示,失去焦点时则显示为默认的凹陷效果。

16.9 表单事件开发

除了focus和blur事件类型与表单紧密关联外,JavaScript还定义了表单专用事件类型,主要包括4种:select、change、submit和reset。这些表单事件具体负责处理form、input、select、button和textarea等表单元素的行为。

16.9.1 选择文本

当在文本框或文本区域内选择输入文本时,将触发select事件。通过该事件,可以设计用户选择操作的交互行为。

【示例】在本示例中当选择第一个文本框中的文本时,则在第二个文本框中会动态显示用户所选择的文本。

16.9.2 监测值变化

change事件类型是在表单元素的值发生变化时触发,它主要用于select和textarea元素。

【示例1】在本示例中,当在第一个文本框中输入或修改值时,则第二个文本框内会立即显示第一个文本框中的当前值。

【示例2】本示例演示了当在下拉列表框中选择不同的网站时,会自动打开该网站的首页。

【示例3】在其他表单元素中也可以应用change事件类型。本示例演示了如何在单选按钮选项组中动态显示变化的值。

由于change事件类型仅在用户已经离开了元素,失去焦点时触发,所以当执行上面3个示例时,会明显感觉延迟响应现象。为了更好地提高用户体验,很多时侯会根据需要定义在按键松开或鼠标单击时执行响应,这样速度会快得很多。

16.9.3 提交表单

submit事件类型仅在表单内单击提交按钮,或者在其中的输入表单元素内按Enter键时触发。

【示例1】在本示例中,当在表单内的文本框中输入文本之后,单击“提交”按钮后,会触发submit事件处理函数,该函数将禁止表单提交数据到服务器,而是弹出提示对话框显示输入的文本信息。

【示例2】在本示例中,当表单内没有包含提交按钮时,在文本框中输入文本之后,只要按Enter键也一样能够触发submit事件。

【示例3】如果要禁止Enter键提交响应,可以监测键盘响应,当按下Enter键时设置其返回值为false,从而取消键盘的默认动作,禁止响应Enter键响应提交行为。

16.9.4 重置表单

reset与submit是一对相反操作的事件类型。当在表单内单击“重置”按钮后,将触发reset,它能够恢复表单内元素的默认值。当然,也可以定义重置事件的具体操作行为。

【示例】在本示例中,当单击“重置”按钮时,会弹出文本框中的输入值,同时会恢复文本框的默认值状态,如果没有默认值,则显示为空。

reset事件与submit事件操作基本相同,但是它不会响应用户的Enter键操作。也就是说,如果没有显示“重置”按钮,则按Enter键是无法触发reset事件的。

16.10 案例实战:自定义事件

无论是从事Web开发,还是从事GUI开发,事件都是经常用到的。很多DOM对象都支持原生的事件,事件机制可以为类型设计带来灵活性。随着Web技术的发展,使用JavaScript自定义对象愈发频繁,为创建的对象绑定事件机制,通过事件对外通信,可以极大提高开发效率。

16.10.1 设计弹出对话框

【示例】事件并不是可有可无的,在某些需求下是必需的。下面示例通过简单的需求说明事件的重要性,在Web开发中对话框是很常见的组件,每个对话框都有一个“关闭”按钮,“关闭”按钮对应关闭对话框的方法。示例初步设计的完整代码如下(test.html),演示效果如图16-9所示。

图16-9 打开对话框

在上面示例中,当单击页面中的“打开对话框”按钮,即可弹出对话框,单击对话框右上角的“关闭”按钮,可以隐藏对话框。

16.10.2 设计遮罩层

【示例】一般对话框在显示时,页面还会弹出一层灰蒙蒙半透明的遮罩层,阻止用户对页面其他对象的操作,当对话框隐藏时,遮罩层会自动消失,页面又能够被操作。执行下面操作,完善上面示例对话框(test1.html)。

第1步,在<body>顶部添加一个遮罩层:

     <div id="pageCover" class="pageCover"></div>

第2步,为其添加样式:

第3步,设计打开对话框时,显示遮罩层,需要修改openDialog方法代码:

第4步,重新设计对话框的样式,避免被遮罩层覆盖,同时清理body的默认边距。

第5步,保存文档,在浏览器中预览,则显示效果如图16-10所示。

图16-10 重新设计对话框

在上面示例中,当打开对话框后,半透明的遮罩层在对话框弹出后,遮盖住页面上的按钮,对话框在遮罩层之上。但是,当关闭对话框时,遮罩层仍然存在页面中,没有代码能够将其隐藏。

如果按照打开时怎么显示遮罩层,关闭时就怎么隐藏。但是,这个试验没有成功,因为显示遮罩层的代码是在页面上按钮事件处理函数中定义的,而关闭对话框的方法存在于Dialog内部,与页面无关,是不是修改Dialog的close方法就可以?也不行,仔细分析有两个原因:

首先,在定义Dialog时并不知道遮罩层的存在,这两个组件之间没有耦合关系,如果把隐藏遮罩层的逻辑写在Dialog的close方法内,那么Dialog将依赖于遮罩层。也就是说,如果页面上没有遮罩层,Dialog就会出错。

其次,在定义Dialog时,也不知道特定页面遮罩层的ID(<div id="pageCover">),没有办法知道隐藏哪个<div>标签。

是不是在构造Dialog时,把遮罩层的ID传入就可以了呢?这样两个组件不再有依赖关系,也能够通过ID找到遮罩层所在的<div>标签,但是如果用户需要部分页面弹出遮罩层,部分页面不需要遮罩层,又将怎么办?即便能够实现,但是这种写法比较笨拙,代码不够简洁、灵活。

16.10.3 定义事件雏形

【示例】上面分析说明,如果简单针对某个具体页面,所有问题都可以迎刃而解,但是如果设计适应能力强,可满足不同用户需求的对话框组件,使用自定义事件是最好的方法。

修改Dialog对象和openDialog方法(test2.html),代码如下:

在Dialog对象内部添加一个句柄(属性),当“关闭”按钮的click事件处理程序在调用close方法后,判断该句柄是否为函数,如果是函数,就调用执行该句柄函数。

在openDialog方法中,创建Dialog对象后为句柄赋值,传递一个隐藏遮罩层的方法,这样在关闭Dialog时,就隐藏了遮罩层,同时没有造成两个组件之间的耦合。

上面这个交互过程就是一个简单的自定义事件,即先绑定事件处理程序,然后在原生事件处理函数中调用,以实现触发事件的过程。DOM对象的事件,如button的click事件,也是类似原理。

16.10.4 设计事件触发模型

【示例】设计高级自定义事件。上面示例简单演示了如何自定义事件,远不及DOM预定义事件抽象和复杂,这种简单的事件处理有很多弊端:

☑ 没有共同性。如果在定义一个组件时,还需要编写一套类似的结构处理。

☑ 事件绑定有排斥性。只能绑定一个close事件处理程序,绑定新的会覆盖之前绑定。

☑ 封装不够完善。如果用户不知道有个close_handler的句柄,就没有办法绑定该事件,只能去查源代码。

针对第一个弊端,可以使用继承来解决;对于第二个弊端,则可以提供一个容器(二维数组)来统一管理所有事件;针对第三个弊端,需要和第一个弊端结合,在自定义的事件管理对象中添加统一接口,用于添加、删除、触发事件。

addHandler()方法用于添加事件处理程序,removeHandler()方法用于移除事件处理程序,所有的事件处理程序在属性handlers中统一存储管理。调用trigger()方法触发一个事件,该方法接收一个至少包含type属性的对象作为参数,触发时会查找handlers属性中对应type的事件处理程序。

下面就可以编写如下代码,来测试自定义事件的添加和触发过程(test3.html)。

16.10.5 应用事件模型

【示例】通过16.10.4节示例,简单分解了高级自定义事件的设计过程,下面示例将利用继承机制解决第一个弊端。

下面是寄生式组合继承的核心代码,这种继承方式是目前公认的JavaScript最佳继承方式。

最后,显示本节完善后的自定义事件的完整代码(test4.html),演示效果如图16-11所示。

图16-11 优化后对话框组件应用效果

用户也可以在打开Dialog时,显示遮罩层也写成类似关闭事件的方式(test5.html)。当代码中存在多个部分,在特定时刻相互交互的情况下,自定义事件就非常有用。

如果每个对象都有其他对象的引用,那么整个代码高度耦合,对象改动会影响其他对象,维护起来就困难重重,自定义事件使对象能够解耦,功能隔绝,这样对象之间就可以实现高度聚合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值