js事件处理

js事件处理

客户端js程序采用了异步事件驱动编程模型(有介绍, 搞个链接). 在这种程序设计风格下, 当文档, 浏览器, 元素或与之相关的对象发生某些有趣的事情时, web浏览器就会产生事件(event).

例如: 当web浏览器加载完文档, 用户把鼠标指针移到超链接上或敲击键盘时, web浏览器都会产生事件. 如果js应用程序关注特定类型的事件, 那么他可以注册当这类事件发生时要调用的一个或多个函数.

注意: 这种风格并不止应用于web编程, 所有使用图形用户界面的应用程序都采用了它, 他们静待某些事情发生(即, 它们等待事件发生), 然后它们响应.

事件就是web浏览器通知应用程序发生了什么事情. 事件不是js对象, 不会出现在程序源代码中. 当然, 会有一些事件相关的对象出现在源代码中, 他们需要技术说明.

事件类型(event type)是一个用来说明发生什么类型事件的字符串.
例如: mousemove表示用户鼠标移动, keydown表示键盘上某个键被按下, 而load表示文档(或某个其他资源)从网络上加载完毕.
由于事件类型只是一个字符串, 因此实际上有时会称之为事件名字(event name), 我们用这个名字来标识所谈论的特定类型的事件.

事件目标(event target)是发生的事件或与之相关的对象.当将事件时, 我们必须同时指明类型(type, name)和目标(target).

例如: window上的load事件或<button>元素的click事件. 在客户端的js应用程序中, Window, DocumentElement对象是最常见的事件目标, 但某些事件是由其他类型的对象触发.比如XMLHttpRequest对象触发的readystatechange事件.

事件处理程序event handle事件监听程序(event listener)是处理或响应事件的函数. 应用程序通过指明事件类型type和事件目标target, 在web浏览器中注册他们的事件处理程序handle, listener. 当在特定的目标上发生特定类型的事件时, 浏览器会调用对应的处理程序. 当对象上注册的事件处理程序被调用时, 我们有时会说浏览器”触发”(fire, trigger)和”派发”(dispatch)了事件. 有很多注册事件处理程序的方法.

事件对象(event object)是与特定事件相关且包含有关该事件详细信息的对象. 事件对象object作为参数传递给事件处理程序函数handle. 所有的事件对象object都有用来指定事件类型的type属性和指定事件目标的target属性. 每个事件类型都为其相关事件对象object定义一组属性.
例如: 鼠标事件的相关对象会包含鼠标指针的坐标, 而键盘事件的相关对象会包含按下的键和辅助键的详细信息. 许多事件类型仅定义了像typetarget这样少量的标准属性, 就无法获取许多其他有用的信息.

事件传播(event propagation)浏览器决定哪个对象触发其事件处理程序的过程. 对于单个对象的特定事件(比如Window对象的load事件), 必须是不能传播的. 当文档元素上发生某个类型的事件时, 然而, 他们会在文档树上向上传播或”冒泡(bubble)”, 如果用户移动鼠标指针到超链接上, 在定义这个链接的<a>元素上首先会触发mousemove事件, 然后是在这个容器元素上触发这个时间, 也许是<p>元素,<div>元素或Document对象本身. 有时, 在Document或其他容器元素上注册单个事件处理程序比在每个独立的目标元素上都注册程序要更方便. 事件处理程序能通过调用方法或设置事件对象属性来阻止事件传播, 这样它就能停止冒泡且将无法在容器元素上触发处理程序.

事件传播的另外一种形式称之为事件捕获(event capturing), 在容器元素上注册的特定处理程序有机会在事件传播倒真实目标之前拦截(或”捕获”)它., 但是,当处理鼠标拖放事件时, 捕获或”夺取”鼠标时间的能力是必须的.

一些事件有与之相关的默认操作. 例如: 当超链接上发生click事件时, 浏览器的默认操作是按照链接加载新页面. 事件处理程序可以通过返回一个适当的值, 调用时间对象的某个方法或设置事件对象的某个属性来阻止默认操作的发生. 这有时称为”取消”事件.

17.1事件类型

web初期,客户端程序员只能使用很少部分事件,load, click, mouseover, 现在有新事件,3个来源:

  • 3级DOM事件(DOM Level Events)规范,经过长期的停滞之后, 在W3Cde主持下又开始焕发生机.
  • HTML5规范及相关衍生规范的大量新API定义了新事件, 比如历史管理, 拖放, 跨文档通信,以及视频和音频的播放.
  • 基于触摸和支持JavaScript的移动设备的出现, 他们需要定义新的触摸和手势事件类型.

注意:许多新事件类型尚未广泛实现, 定义它们的标准也依旧处于草案阶段.

事件分类: 大致先分6类吧

1依赖与设备的输入事件:
有些事件和特定输入设备直接相关, 比如鼠标和键盘. 包括诸如mousedown, mousemove,mouseup,keypress,keyup这样传统事件类型, 也包括像touchmove,getsturcchange这样新的触摸事件类型

2独立于设备的输入事件:
有些输入事件没有直接相关的特定输入设备. 比如click事件表示激活了链接, 按钮或其他文档元素, 这通常是通过鼠标单击实现, 但也能通过键盘或触控感知设备上的手势来实现. 尚未广泛实现的textinput事件就是一个独立与设备的输入事件, 他既能取代按键事件并支持键盘输入, 也可以取代剪切和粘贴与手写识别的事件.

3用户界面事件:
用户界面事件是比较高级的事件, 通常出现在定义web应用用户界面的HTML表单元素上. 包括文本输入域获取键盘焦点的focus事件, 用户改变表单元素显示值的change事件和用户单击表单中的提交按钮的submit事件

4状态变化事件:
有些事件不是由用户活动而是由网络或浏览器活动触发, 用来表示某种生命周期或相关状态的变化. 当文档完全加载时, 在Window对象上会发生load事件, 这可能是这类事件中最常用的, DOMContentLoaded事件与此类似, HTML5历史管理机制会触发popstate事件来响应浏览器的后退按钮. HTML5离线web应用API包括onlineoffline事件. 当向服务器请求的数据准备就绪事, 如何利用readystatechange事件得到通知, 类似的, 用于读取用户选择本地文件中的新API使用像loadstate, progressloadend事件来实现I/O过程的异步通知.

5特定API事件:
HTML5及相关规范定义的大量web API都有自己的事件类型. 拖放API定义了诸如dragstart, dragenter, dragoverdrop事件, 应用程序想自定义拖放源(drag source)或拖放目标(drop target)就必须处理这些相关事件. HTML5的<video><audio>元素定义一长串像waiting, playibg, seekingvolumechange等相关事件, 这些事件通常仅用于web应用, 这些web应用希望为视频和音频的播放定义自定义控件.

6计数器和错误处理程序:
在第14章中介绍过计时器(timer)和错误处理程序(error handler)属于客户端JavaScript异步编程模型的部分, 并有相似的事件.

17.1.1传统事件类型

处理鼠标, 键盘, HTML表单和window对象的事件都是web应用中最常用的, 他们已经存在很长的时间并得到了广泛的支持.

1.表单事件

回到web和JavaScript的早期, 表单和超链接都是网页中最早支持脚本的元素. 这就意味着表单事件是所有事件类型中最稳定且得到良好支持的那部分.

  • 当提交表单和重置表单时, <form>元素会分别触发submitreset事件.
  • 当用户和类按钮表单元素(包括单选按钮和复选框)交互时, 他们会发生click事件.
  • 当用户通过输入文字, 选择选项或选择复选框来改变相应表单元素的状态时, 这些通常维护某种状态的表单元素会触发change事件.
  • 对于文本输入域, 只有用户和表单元素完成交互并通过Tab键或单击的方式移动焦点到其他元素上时才会触发change事件.响应通过键盘改变焦点的表单元素在得到和失去焦点时会分别触发focusblur事件

15.9.3节涵盖了所有表单相关事件的详细信息, 不过还有一些进一步说明. 通过事件处理程序能取消submitreset事件的默认操作, 某些click事件也是如此. focusblur事件不会冒泡, 但其他所有表单事件都可以. IE定义了focusinfocusout事件可以冒泡, 他们可以用于替代focusblur事件. jQuery库为不持之focusinfocusout事件的浏览器模拟了这两个事件, 同时3级DOM事件规范也正在标准化他们.

最后注意, 无论用户何时输入文字(通过键盘或剪切和粘贴)到<textarea>和其他文本输入表单元素, 除IE外的浏览器都会触发input事件. 不像change事件, 每次文字插入都会触发input事件. 遗憾的是, input事件的事件对象没有指定输入文本的内容 (稍后介绍的textinput事件将会成为这个事件的有用替代方案)

2.Window事件

window事件是指事件的发生于浏览器窗口本身而非窗口中显示的任何特定文档内容相关. 但是, 这些事件中有一些会和文档元素发生的事件同名.

load事件是这些事件中最重要的一个, 当文档和其所有外部资源(比如图片)完全加载并显示给用户时就会触发它. 有关load事件的讨论贯穿整个第13章. DOMContentLoadedreadystatechangeload事件的替代方案, 当文档和其他元素为操作准备就绪, 但外部资源完全加载完毕之前, 浏览器就会尽在触发他们. 17.4有这些与文档加载相关事件的示例.

unload事件和load相对, 当用户离开当前文档转向其他文档时会触发它. unload事件处理程序可有用于保护用户的状态, 但他不能用于取消用户转向其他地方. beforeunload事件和unload事件类似, 但他能提供询问用户是否正确离开当前页面的机会. 如果beforeunload的处理程序返回字符串, 那么在新页面加载之前, 字符串会出现在展示给用户确认的对话框上, 这样用户就有机会取消其跳转而留在当前页上.

window对象的onerror属性有点像事件处理程序, 当JavaScript出错时会触发它. 但是, 他不是真正的事件处理程序, 因为他能用不同的参数来调用. 更多详细信息看14.6节

<img>元素这样的单个文档元素也能为loaderror事件注册处理程序. 当外部资源(例如图片)完全家在或发生阻止加载的错误时就会触发它们. 某些浏览器也支持abort事件(HTML5将其标准化), 当图片(或其他网络资源)因为用户停止加载进程而导致失败就会触发它.

前面介绍的表单元素的focusblur事件也能用做Window事件, 当浏览器窗口从操作系统中得到或失去键盘焦点时会触发它们.

最后, 当用户调整浏览器窗口大小或滚动它时会触发resizescroll事件.scroll事件也能在任何可以滚动的文档元素上触发, 比如那些设置CSSoverflow属性的元素. 传递给resizescroll事件处理程序的事件对象是一个非常普遍的Event对象, 他没有制定调整大小或发生滚动的详细信息属性, 但可以通过15.8节介绍的技术来确定新窗口的尺寸和滚动条的位置.

3.鼠标事件

当用户在文档上移动或单击鼠标时都会产生鼠标事件. 这些事件在鼠标指针所对应的最深嵌套元素上触发, 但他们会冒泡直到文档最顶层. 传递给鼠标事件处理程序的事件对象有属性集, 它们描述了当事件发生时鼠标的位置和按键状态, 也指明了当时是否有任何辅助键按下. clientXclientY属性制定了鼠标在窗口坐标中的位置. buttonwhich属性指定了按下的鼠标键是哪个.(无论如何请看Event参考页, 因为这些属性难以简单使用.) 当键盘辅助键按下时, 对应的属性altkey, ctrlKey, metaKeyshiftKey会设置为true. 而对于click事件, detail事件指定了其是单击, 双击还是三击.

用户每次移动或拖动鼠标时, 会触发mousemove事件. 这些事件的发生非常繁琐, 所以mousemove事件处理程序一定不能触发计算密集型任务. 当用户按下或释放鼠标按键时, 会触发mousedownmousemove事件. 通过注册mousedownmousemove事件处理程序, 可以探测和响应鼠标的拖动. 合理地这样做能捕获鼠标事件, 甚至当鼠标从开始元素移出时我们都能持续地接收到mousemove事件.

mousedownmouseup事件队列之后, 浏览器也会触发click事件. 之前介绍过click事件是独立于设备的表单事件, 但实际上他不仅仅在表单元素上触发, 他可以在任何文档元素上触发, 同时传递拥有之前介绍的所有鼠标相关额外字段的事件对象. 如果用户在相当短的时间内连续两次单击鼠标按键, 跟在第二个click事件之后是dblick事件. 当单击鼠标右键时, 浏览器通常会显示上下文菜单(context menu). 在显示菜单之前, 他们通常会触发contextmenu事件, 而取消这个事件就可以阻止菜单的显示. 这个事件也是获得鼠标右击通知的简单方法.

当用户移动鼠标指针从而使他悬停到新元素上时, 浏览器就会在该元素上触发mouseover事件. 当鼠标移动指针从而使他不在悬停在某个元素上时, 李兰器就会在该元素上触发mouseout事件. 对于这些事件, 事件对象将有relatedTarget属性指明这个过程设计的其他元素. (到Event参考页查看relatedTarget属性的IE等效属性) mouseovermouseout事件和这里介绍的素有鼠标事件一样会冒泡. 但这通常不方便, 因为当触发mouseout事件处理程序时, 你不得不检查鼠标是否真的离开目标元素还是仅仅是从这个元素的一个子元素移动到另一个. 正因为如此, IE提供了这些事件的不冒泡版本mouseentermouseleave, JQuery模拟非IE的浏览器中这些事件的支持, 同时3级DOM事件规范把它们标准化了.

当用户滚动鼠标滚轮时, 浏览器触发mousewheel事件(或在firefox中是DOMMouseScroll事件). 传递的时间对象属性指定滚轮转动的大小和方向. 3级DOM事件规范正在标准化一个更通用的多维wheel事件, 一旦实现将取代mousewheelDOMMouseScroll事件.

4.键盘事件

当键盘聚焦到Web浏览器时, 用户每次按下或释放键盘上的按键时都会产生事件. 键盘快捷键对于操作系统和浏览器本身有特殊意义, 他们经常被操作系统或浏览器”吃掉”, 并对JavaScript事件处理程序不可见. 无论任何文档元素获取键盘焦点都会触发键盘事件, 并且他们会冒泡到DocumentWindow对象. 如果没有元素获取焦点, 可以直接在文档上触发事件. 传递给键盘事件处理程序的事件对象有keyCode字段, 他指定按下或释放的键是哪个. 除了keyCode, 键盘事件对象也有altKey, ctrlKey, metaKeyshiftKey, 描述键盘辅助键的状态.

keydownkeyup事件是低级键盘事件, 无论何时按下或释放按键(甚至是辅助键)都会触发他们.
keydown事件产生可打印字符时, 在keydownkeyup之间会触发另外一个keypress事件.
当按下键重复产生字符时, 在keyup事件之前可能产生很多keypress事件. keypress是较高级的文本事件, 其事件对象指定产生的字符而非按下的键.

所有浏览器都支持keydown, keyupkeypress事件, 但有一些互用性问题, 因为事件对象的keyCode属性值从未标准化过. 3级DOM事件规范尝试解决之前的互用性问题, 但尚未实施.

17.1.2 DOM事件

W3C开发3级DOM事件规范已经长达十年之久. 现在终于处于标准化的”最后征集工作草案”阶段, 它标准化了前面介绍的许多传统事件, 同时增加了这里介绍的一些新事件. 这些新事件类型尚未得到广泛支持, 一旦标准确定, 我们就期望浏览器厂商能实现他们.

如上所述, 3级DOM事件规范标准化了不冒泡的focusinfocusout事件来取代冒泡的focusblur事件. 此版本的标准也弃用了大量由2级DOM事件规范定义但未得到广泛实现的事件类型. 浏览器依旧允许产生像DOMActive, DOMFocusInDOMNodeInserted这样的事件, 但他们不在必要, 同时本书的文档也不会列出他们(在名字中使用”DOM”的唯一常用事件就是DOMContentLoaded, 这个事件由Mozilla引入, 但绝不属于DOM事件标准的一部分).

3级DOM事件规范中新增内容有通过wheel事件对二维鼠标滚轮提供标准支持, 通过textinput事件和传递新KeyboardEvent对象作为参数给keydown, keyupkeypress的事件处理程序来给文本输入事件提供更好的支持.

wheel事件的处理程序接收到的事件对象除了所以普通鼠标事件属性, 还有delatX, delatYdelatZ属性来报告三个不同的鼠标滚轴. 大多数鼠标滚轮是一维或二维的, 并不使用delatZ. 更多关于mousewheel事件的内容请参见17.6节.

如上所述, 3级DOM事件规范定义了keypress事件, 但不赞成使用它而使用称为textinput的新事件. 传递给textinput事件处理程序的时间对象不再有难以使用的数字keyCode属性值, 而有指定输入文本字符串的data属性. textinput事件不是键盘特定事件, 无论通过键盘, 剪切和粘贴, 拖放方式, 每当发生文本输入时就会触发它. 规范定义了时间对象的inputMethod属性和一组代表各种文本输入种类的常量(键盘, 粘贴, 拖放,手写和语音识别等). 在写本章时, Safari和Chrome使用混合大小写的textInput来支持这个事件版本, 其事件对象有data属性但没有inputMethod属性.

新DOM标准通过在事件对象中加入新的keychar属性来简化keydown, keyupkeypress事件, 这些属性都是字符串. 对于产生可打印字符的键盘事件, keychar值将等于生成的文本. 对于控制键, key属性将会是像标识键的”Enter“, “Delete“和”Left“这样的字符串, 而char属性将是null, 或对于像Tab这样的控制键有一个字符编码, 它将是按键产生的字符串. 在写本章时, 尚未有浏览器支持keychar属性.

17.1.3 HTML5事件

HTML5及相关标准定义了大量新的web应用API(第22章), 其中许多API都定义了事件. 本节列出并简要介绍这些HTML5和web应用事件. 其中一些事件现在已经可以开始使用.

广泛推广的HTML特性之一是加入用于播放音频和视频的<audio><video>元素. 这些元素有长长的事件列表, 他们触发各种关于网络事件, 数据缓冲状况和播放状态的通知:

1
2
3
4
5
canplay         loadeddata      playing     stalled
canplaythrough loadedmetadata progress suspend
durationchange loadstart ratechange timeupdate
emptied pause seeked volumechange
ended play seeking waiting

传递给美体时间处理程序的事件对象普通且没有特殊属性, target属性用于识别<audio><video>元素, 然而这些元素有多相关的属性和方法. 21.2节有更多关于这些元素及其属性和事件的详细内容.

HTML5的拖放API允许JavaScript应用参与基于操作系统的拖放操作, 实现web和原生应用间的数据传输. 该API定义了如下7个事件类型:

1
2
3
dragstart       drag        dragend
dragenter dragover dragleave
drop

触发拖放事件的事件对象和通过鼠标事件发送的对象类似, 其附加属性dataTransfer持有DataTransfer对象, 它包含关于传输的数据和其中可用的格式信息.

HTML5定义了历史管理机制, 它允许web应用同浏览器的返回和前进按钮交互. 这个机制涉及的事件是hashchangepopstate, 这些事件是类似loadunload的生命周期通知事件, 他在Window对象上触发而非任何单独的文档元素.

HTML5为HTML表单定义了大量的新特性. 除了标准化前面介绍的表单输入事件外, HTML5也定义了表单验证机制, 包括当验证失败时在表单元素上会触发invalid事件. 除Opera外的浏览器厂商已经慢慢实现HTML5的新表单特性和事件, 但本书没有涵盖他们.

HTML5包含了对离线web应用的支持, 他们可以安装到本地应用缓存中, 所以即使路蓝旗离线时它们依旧能运行, 比如当移动设备不在网络范围内时. 相关的两个最重要的事件是offlineonline, 无论何时浏览器失去或得到网络连接都会在Window对象上触发它们. 标准还定义了大量其他事件来通知应用下载进度和应用缓存更新:

1
2
cached      checking        downloading     error
noupdate obsolete progress upateready

很多新web应用API都使用message事件进行异步通信. 跨文档通信API允许一台服务器上的文档脚本能和另一台服务器上的文档脚本交换信息. 其工作受限于同源策略这一安全方式. 发送的每一条消息都会在接受文档的Window上触发message事件. 传递给处理程序的事件对象包含data属性, 它有保存信息内容以及用于识别消息发送者的source属性和origin策略. message事件的使用方式与使用Web Worker通信, 通过Server-Sent事件和WebSocket进行网络通信相似.

HTML5及相关标准定义了一些不在窗口, 文档和文档元素的对象上触发的事件. XMLHttpRequest规范第2版和File API规范都定义了一系列事件来跟踪异步I/O的进度. 它们在XMLHttpRequestFileReader对象上触发事件. 每次读取操作都是以loadstart事件开始, 接着是progressloadend事件. 此外, 每个操作仅在最终loadend事件之前会有load,errorabort事件.

最后, HTML5及相关标准定义了少量庞杂的事件类型. 在Window对象上发生的web存储API定义了storage事件用于通知存储数据的改变. HTML5页标准化了最早由Microsoft在IE中引入的beforeprintafterprint事件. 顾名思义, 当文档打印之前或之后立即在Window对象触发这些事件, 它提供了打印文档时添加或删除类似日期或事件等内容的机会. (这些事件不应该用于处理打印文档的样式, 因为CSS媒体类型更适合这个用途.)

17.1.4 触摸屏和移动设备事件