# 事件

客户端 javascript 程序采用了异步事件驱动编程模式。在这种程序设计风格下，当文档、浏览器、元素或与之相关的对象发生某些有趣的事情时，Web 浏览器就会产生事件。

### 事件类型

#### 事件分类

事件大致可以分成几类：

1. 依赖于设备的输入事件：有些事件和特定输入设备直接相关，如果鼠标和键盘。包括诸如`mousedown`、`mousemove`、`mouseup`、`keydown`、`keypress`和`keyup`这样的传统事件类型，也包括像`touchmove`和`gesturechange`这样新的触摸事件类型。
2. 独立于设备的输入事件：有些输入事件没有直接相关的特定输入设备。例如，`click` 事件表示激活了链接、按钮或其他文档元素，这通常是通过鼠标单击实现，单也能通过键盘或触摸感知设备上的手势来实现。
3. 用户界面事件：用户界面事件是教高级的事件，通常出现在 Web 应用用户界面的 HTML 表单元素上。包括文本输入域获取键盘焦点的`focus`事件、用户改变表单元素显示值的`change`事件和用户单击表单中的“提交”按钮的`submit`事件。
4. 状态变化事件：有些事件不是由用户活动而是由网络或浏览器活动触发，用来表示某种生命周期或相关状态的变化。比如当文档完全加载是，在`Window`对象上会发生`load`事件。
5. 特定 API 事件：HTML5 及相关规范定义的大量 Web API 都有自己的事件类型。拖放 API 定义了诸如`dragstart`、`dragenter`、`dragover`和`drop`事件，应用程序想自定义拖放源或拖放目标就必须处理这些相关事件。
6. 计时器和错误处理程序：计时器和错误处理程序属于客户端 javascript 异步编程模型的部分，并有相似的事件。

#### Window 事件

`WIndow` 事件是指事件的发生与浏览器窗口本身而非窗口中显示的任何特定文档内容相关。

`load` 事件是这些事件中最重要的一个，当文档和其所有外部资源完全加载并显示给用户时就会触发它。

`unload` 事件和 `load` 相对，当用户离开前文档转向其他文档时会触发它。`beforeunload` 事件和 `unload` 类似，但它能提供询问用户是否确定离开当前页面的机会。

`Window` 对象的 `onerror` 属性有点像处理程序，当 javascript 出错时才会触发。某些浏览器也支持 `abort` 事件，当图片(或其他网络资源)因为用户停止加载进程而导致失败就会触发它。

当浏览器从操作系统中得到或失去键盘焦点时会触发 `focus` 和 `blur` 事件。

当用户调整浏览器窗口大小或滚动它时会触发 `resize` 和 `scroll` 事件。

#### 鼠标

传递给鼠标事件处理程序的事件对象有属性集，它们描述了当事件发生时鼠标的位置和按键状态，也指明当时是否有任何辅助键按下。`clientX` 和 `clientY` 属性指定了鼠标在窗口坐标中的位置，`button` 和 `which` 属性指定了按下的鼠标键是哪个。当键盘辅助按下时，对应的属性 `altKey`、`ctrlKey`、`metaKey` 和 `shiftkey` 会设置为 `true`。而对于 `click` 事件，`detail` 属性指定了其是单击、双击还是三击。

用户每次移动或者拖动鼠标时，会触发 `mousemove` 事件。等用户按下或释放鼠标按键时，会触发 `mousedown` 和 `mouseup` 事件。

如果用户在相当短的时间内连续两次单击鼠标按键，跟在第二个 `click` 事件之后是 `dblclick` 事件。当单击鼠标右键时，浏览器通常会显示上下文菜单。在显示菜单之前，它们通常会触发 `contextmenu` 事件。

当用户移动鼠标指针从而是它悬停到新元素上时，浏览器就会在该元素上触发 `mouseover` 事件。

当用户滚动鼠标滚轮时，浏览器触发 `mousewheel` 事件(或在 Friefox 中是 DOMMouseScroll 事件)。

#### 键盘

当键盘聚焦到 Web 浏览器时，用户每次按下或释放键盘上的按键时都会产生事件。传递给键盘事件处理程序的事件对象有 `keyCode` 字段，它指定按下或释放的键是哪个。除此之外，键盘事件对象也有 `altKey`、`ctrlKey`、`metaKey` 和 `shiftKey`，描述键盘辅助键的状态。

`keydown` 和 `keyup` 事件是低级键盘事件，无论何时按下或释放按键都会触发它们。当 `keydown` 事件产生可打印字符串时，在 `keydown` 和 `keyup` 之间会触发另一个 `keypress` 事件。当按下键重复产生字符时(按住不放)，在 `keyup` 事件之前可能产生很多 `keypress` 事件。`keypress` 是较高级的文本事件，其事件对象指定产生的字符而非按下的键。

#### DOM

`wheel` 事件的处理程序接收到事件对象除了所有普通鼠标事件属性，还有 `deltaX`、`deltaY` 和 `deltaZ` 属性来报告三个不同的鼠标滚轴。

`3` 级 `DOM` 事件规范定义了 `keypress` 事件，但不赞成使用它而使用称为 `textinput` 的新事件。传递给 `textinput` 事件处理程序的事件对象不再有难以使用的数字 `keyCode` 属性值，而有指定输入文本字符串的 `data` 属性。`textinput` 事件不是键盘特定事件，无论通过键盘、剪切和粘贴、拖放等方式，每当发生文本输入时就会触发它。规范定义了事件对象的 `inputMethod` 属性和一组代表各种文本输入种类的常量。

#### HTML5

广泛推广的 HTML5 特性之一是加入用于播放音频的 `audio` 和 `video` 元素。这些元素有长长的事件列表，它们触发各种关于网路事件、数据缓冲状况和播放状态的通知：

```javascript
canplay          loadeddata       playing      stalled
canplaythrough   loadedmetadata   progress     suspend
durationchange   loadstart        ratechange   timeupdate
emptied          pause            seeked       volumechange
ended            play             seeking      waiting
```

HTMl5 的拖放 API 允许 javascript 应用参与基于操作系统的拖放操作。实现 Web 和原生应用间的数据传输。该 API 定义了如下 7 个事件类型：

```javascript
dragstart   drag       drageend
dragenter   dragover   dragleave
drop
```

HTML5 定义了历史管理机制，它允许 Web 应用同浏览器的返回和前进按钮交互，这个机制涉及的事件是 `hashchange` 和 `popstate`。

HTML5 包含了对离线 Web 应用的支持，它们可以安装到本地应用缓存中，所以即使浏览器离线时它们依旧能运行。相关的两个最重要的事件是 `offline` 和 `online`，无论何时浏览器失去或得到网路链接都会在 Window 对象上触发它们。标准还定义了大量其他事件来通知应用缓存更新：

```javascript
cached     checking   downloading   error
noupdate   obsolete   progress      updateready
```

很多新 Web 应用 API 都使用 `message` 事件进行异步通信。跨文档通信 API 允许一台服务器上的文档脚本能和另一台服务器上的文档脚本交换信息。发送的每一条消息都会在接收文档的 Window 上触发 `message` 事件。传递给处理程序的事件包含 `data` 属性，它有保存信息内容以及用于识别消息发送者 `source` 属性和 `origin` 策略。

`XMLHttpRequest` 规范第二版和 `File API` 规范都定义了一系列事件来跟踪异步 `I/O` 的进度、它们在 `XMLHttpRequest` 或 `FileReader` 对象上触发事件。每次读取操作都是以 `loadstart` 事件开始，接着是 `progress` 和 `loadend` 事件。此外，每个操作仅在最终 `loadend` 事件之间会有 `load`、`error` 或 `abort` 事件。

HTMl 及相关标准定义了少量庞杂的事件类型。在 Window 对象上发送的 Web 存储 API 定义了 `storage` 事件用于通知存储数据的改变。HTML5 也标准化了 `beforeprint` 个 `afterprint` 事件，即当文档打印之前或之后立即在 Window 对象上触发这些事件。

#### 触摸屏和移动设备

Safari 产生的手势用于两个手指的缩放和旋转手势。当手势开始生成 `gesturestart` 事件，而手势结束时生成 `gestureend` 事件。在这两个事件之间是跟踪手势过程的 `gesturechange` 事件队列。这些事件传递的事件对象有数字属性 `scale` 和 `rotation`。`scale` 属性是两个手指之间当距离和初始距离的比值。`rotation` 属性是指从事件开始手指旋转的角度，它以度为单位，正值表示按照顺时针方向旋转。

手势事件是高级事件，用于通知已经翻译的手势。如果实现自定义手势，你可以监听低级触摸事件。当手指触摸屏幕时会触发 `touchstart` 事件，当手指移动时会触发 `touchmove` 事件。当手指离开屏幕时会触发 `touchend` 事件。触摸事件并不会直接报告触摸的坐标。相反，触摸事件传递的事件对象有一个 `changeTouches` 属性，该属性是一个类数组对象，其每个元素都描述触摸的位置。

当设备允许用户从竖屏模式选转到横屏模式时会在 Window 对象上触发 `orientationchanged` 事件，该事件传递的事件对象本身没有用。但是，在移动版的 `Safari` 中，Window 对象的 `orientation` 属性能给出当前方位，其值是 `0`、`90`、`180` 或者`-90`。

### 注册事件

#### addEventListener()

Window 对象、Document 对象和所有文档元素都定义了一个名为 `addEventListener()`的方法，使用这个方法可以为事件目标注册事件处理程序。其接受三个参数。第一个是要注册处理程序的事件类型，这个事件类型是字符串，但它不需要用于处理程序属性的前缀`on`。第二个参数是当指定类型的事件发生时应该调用的函数。最后一个参数是布尔值。通常情况下，会给这个参数传递 `false`。如果相反传递了 `true`，那么函数将注册为捕获事件处理程序，并在事件不同的调度阶段调用。一般情况忽略第三个参数并无须传递 `false`。

相对于 `addEventListener()`的是 `removeEventListener()`方法，它同样有三个参数，从对象中删除事件处理程序函数，它常用于临时注册事件处理程序，然后不久就删除它。但需要注意的是匿名函数是无法移除的。

### 事件调用

#### 事件处理程序的参数

通常调用事件处理程序时把事件对象作为它们的一个参数。事件对象的属性提供了有关事件的详细信息。在 IE8 及以前版本中，通过设置属性注册事件处理程序，当调用它们时并未传递事件对象。取而代之，需要通过全局对象 `window.event` 来获得事件对象。出于互通性，可以按下编写事件处理程序：

```javascript
function handler(event) {
  event = event || window.event
  // 处理程序代码出现在这里
}
```

#### 事件处理程序的运行环境

当通过设置属性注册事件处理程序时，这看起来好像是在文档元素上定义新方法：

```javascript
e.onclick = function() {
  /* 处理程序代码 */
}
```

在事件处理程序内，`this` 关键指的是事件目标。使用 `addEventListener()`注册时，调用的处理程序使用事件目标作为它们的 `this` 值。

#### 事件处理程序的返回值

通过设置对象属性或 HTML 属性注册事件处理程序的返回值有时是非常有意义的。通常情况下，返回值 `false` 就是告诉浏览器不要执行这个事件相关的默认操作。例如，表单提交按钮的 `onclick` 事件处理程序能返回 `false` 阻止浏览器提交表单。

#### 调用顺序

文档元素或其他对象可以为指定事件类型注册多个事件处理程序。当适当的事件发生时，浏览器必须按照如下规则调用所有的事件处理程序：

1. 通过设置对象属性或 HTML 属性注册的处理程序一直优先调用。
2. 使用 `addEventListener()`注册的处理程序按照它们的注册顺序调用。
3. 使用 `attachEvent()`注册的处理程序可能按照任何顺序调用，所以代码不应该依赖于事件调用顺序。

#### 事件传播

当事件目标是 Window 对象或其他一些单独对象(比如 XMLHttpRequest)时，浏览器简单的通过调用对象上适当的处理程序响应事件。当事件目标是文档或文档元素时，情况则比较复杂。

调用在目标元素上注册的事件处理函数后，大部分事件会“冒泡”到 `DOM` 树根。调用目标的父元素的事件处理程序，然后调用在目标的祖父元素上注册的事件处理程序。这会一直到 `Document` 对象，最后到达 Window 对象。事件冒泡为在大量单独文档元素上注册处理程序提供了替代方案，即在共同的祖先元素上注册一个处理来处理所有的事件。例如。可以在 `form` 元素上注册`change`事件处理程序来代表表单在每个元素上注册`change`事件处理程序。

事件冒泡是事件传播的第三个“阶段”。目标对象本身的事件处理程序调用是第二个阶段。第一个阶段甚至发生在目标处理程序调用之前，称为“捕获”阶段(设置 addEventListener 的第三个参数为 true 所对应的事件会在此处执行)。事件传播的捕获阶段像反向的冒泡阶段。最先调用 Window 对象的捕获处理程序，然后是 `Document` 对象的捕获处理程序，接着是 `body` 对象的，再然后是 `DOM` 树向下，以此类推，直到调用事件目标的父元素的捕获事件处理程序。在目标对象本身上注册的捕获事件处理程序不会被调用。

事件捕获提供了在事件没有送达目标之前查看它们的机会。事件捕获能用于程序调试，或用于后面介绍的事件取消技术，过滤掉事件从而使目标事件处理程序绝不会被调用。事件捕获常用于处理鼠标拖放，因为要处理拖放事件的位置不能是这个元素内部的子元素。

#### 事件取消

在支持 `addEventListener()`浏览器中，可以通过事件对象的 `preventDefault()`方法取消事件的默认操作。

当前的 `DOM`事件模型草案定义了 `Event` 对象属性 `defaultPrevented`。常态下属性为 `false`，如果 `preventDefaut()`被调用则他将变为 `true`。

在支持 `addEventListener()`的浏览器中，可以调用事件对象的一个 `stopPropagation()`方法以阻止事件的继续传播。其可以在事件传播期间的任何时间调用，它能工作在捕获阶段、事件目标本身和冒泡阶段。

`stopImmediatePropagation()`不仅阻止了任何其他对象的事件的传播，还阻止了在相同对象上注册的任何其他事件处理程序的调用。

### 文档加载事件

`document.readyState` 属性随着文档加载过程而变。HTMl5 标准化了 `readystatechange` 事件。

```javascript
document.addEventListener('readystatechange', handler, false)
```

在 `handler` 函数中可以通过 `e.type` 得到 `readystatechange`；

### 鼠标事件

除了`mouseenter`和`mouseleave`外所有的鼠标事件都能冒泡。

1. `click`：高级事件，当用户按下并释放鼠标按键或其他方式“激活”元素是触发。
2. `contextmenu`：可取消事件。当前浏览器在鼠标右击显示上下文菜单。
3. `dblclick`：当用户双击鼠标时触发。
4. `mousedown`：当用户按下鼠标按键时触发。
5. `mouseup`：当用户释放鼠标按键时触发。
6. `mousemove`：当用户移动鼠标时触发。
7. `mouseover`：当鼠标进入元素触发。`relatedTarget` 指的是鼠标来自的元素。
8. `mouseout`：当鼠标离开元素时触发。`relatedTarget` 指的是鼠标要去往的元素。
9. `mouseenter`：类似`mouseover`。
10. `mouseleave`：类似`mouseout`。

传递给鼠标事件处理程序的事件对象有 `clientX` 和 `clientY` 属性，它们指向指针相对于包含窗口的坐标。

`altKey`、`CtrlKey`、`metaKey` 和 `shiftKey` 属性指定了事件发生时是否有各种键盘辅助键按下。`button` 属性指定当前事件发生时哪个鼠标键按下。

### 滚轮事件

3 级 `DOM` 事件规范草案标准定义了 `wheel` 事件作为 `mousewheel` 和 `DOMMouseScroll` 的标准版本。传递给 `wheel` 事件处理程序的事件对象属性使用输出 `e` 来查看。

### 文本事件

建议中的 `textinput` 事件和已经实现的 `textInput` 事件都传递一个简单的事件对象。它有一个用于保存输入文本的 `data` 属性。经测试火狐不支持这一事件。`keypress`为所有浏览器通用事件。

可以通过取消 `textinput`、`textInput` 和 `keypress` 事件来阻止字符串输入。

`keypress` 和 `textinput` 事件是在新输入的文本真正插入到聚焦的文档元素前触发，这就是这些事件处理程序能够取消事件和阻止文本插入的原因。浏览器也实现了在文本插入到元素后才触发的 `input` 事件类型 `input`。虽然这些事件不能取消，不能指定其事件对象中的最新文本，但它们能以某种形式提供元素文本内容发生改变的通知。在 IE 中，可以使用不标准的 `propertychange` 事件检测文本输入元素的 `value` 属性改变来实现相似的功能。

### 键盘事件

当用户在键盘上按下或释放按键时，会发生 `keydown` 和 `keyup` 事件。它们由辅助键、功能键和字母数字键产生，如果用户按键时间足够长会导致它开始重复，那么在 `keyup` 事件到达之前会收到多个 `keydown`。

这些事件相关的事件对象都有数字属性 `keyCode`，指定了按下的键是哪个。对于产生可打印字符的按键，`keyCode` 值是按键上出现的主要字符的 `Unicode` 编码。无论 `Shift` 键出于什么状态，字母键总是产生大写 `keyCode` 值，这是因为它们出现在物理键盘上。

类似鼠标事件对象，键盘事件对象有 `altKey`、`ctrlKey`、`metaKey` 和 `shiftKey` 属性。当事件发生时，如果对应的辅助键被按下，那么它们会被设置为 `true`。

3 级 `DOM` 事件规范草案标准化了 `keydown` 和 `keyup` 事件类型，它定义了新属性 `key`，它会以字符串的形式包含键名。如果按键对应的是一个可打印字符，那么 `key` 属性将仅仅是这个可打印字符。如果按键是功能键，那么 `key` 属性是像`F2`、`Home`这样的值。
