JavaScript 事件系统
事件系统概述
基础概念
JavaScript 和 HTML 之间的交互是通过事件来实现的。事件,就是文档或浏览器窗口之间发生的一些交互瞬间。可以使用侦听器(或处理程序)来监听事件,以便事情发生时执行相应的代码。
一个完整的事件系统,通常存在以下三个角色:
- 事件对象,用于储存事件的状态。
- 事件源对象,当前事件在操作的对象,如元素节点,文档对象,window 对象,XMLHttpRequest 对象等。
- 事件监听函数,当一个事件源生成一个事件对象时,它会调用相应的回调函数进行操作。
通俗点讲,事件源对象相当于”当事人“,事件监听函数相当于”监护人“,事件对象相当于”事故详情“。一个事件可以理解为,当事人出了点事,至于什么事情(被打了,还是被抢了)都记录在事故 详情里,监护人根据事故详情得做出点反应(回调函数)。
历史
事件最早是在 IE3 和 Netscape Navigator2 中出现的,当时是作为分担服务器运算负载的一种手段。到 IE4 和 Navigator4 发布时,这两种浏览器都提供了相似但不相同的 API ,而且这些 API 并存且经历了好几个版本更新。再后来,DOM2 级规范开始尝试以一种符合逻辑的方式来标准化 DOM 事件。
IE9、Firefox、Opera、Safari 和 Chrome 全都已经实现了”DOM2 级事件“模块的核心部分。IE8 是最后一个仍然使用其专有事件系统的主要浏览器。
浏览器的事件系统相对比较复杂。尽管所有主要浏览器已经实现了”DOM2 级事件“,但这个规范本身并没有涵盖所有的事件类型。浏览器对象模型(BOM)也支持一些事件,这些事件与文档对象模型(DOM)事件之间的关系并不十分清晰,因为 BOM 事件长期没有规范可以遵循(HMTL5 后来给了详细说明)。随着 DOM3 级的出现,增强后的 DOM 事件 API 变的更加繁琐。使用 事件有时相对简单,有时则非常复杂,难易程度会因为你的需求而不同。不过,有关事件的一些核心概念是一定要理解的。
事件传播
当浏览器发展到第四代时(IE4 及 Netscape Communicator4),浏览器开发团队遇到了一个很有意思的问题。如下图所示,当我们点击目标事件的时候,不仅点击了自身,也点击了自身的容器,甚至点击了整个页面。如果这些元素都绑定了点击事件,那事件的执行顺序应该是怎样的?(暂时可以忽略图中的文字性描述)

事件流描述的就是从页面中接受事件的顺序。但有意思的是,IE 和 Netscape 团队提出了几乎完全相反的事件流概念。IE 的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕获流。
事件冒泡流与事件捕获流
- 事件冒泡流:事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。(由内及外)
- 事件捕获流:由不太具体的节点更早接收到事件,而最具体的节点应该最后接收到事件。(由外及内)

所有现代浏览器都支持事件冒泡,但在具体实现中略有差别。IE5.5 及更早版本中事件冒泡会跳过 html
元素(从 body 直接跳到 document)。IE9、Firefox、Chrome、和 Safari 则将事件一直冒泡到 window 对象。
IE9、Firefox、Chrome、Opera、和 Safari 都支持事件捕获。尽管 DOM 标准要求事件应该从 document 对象开始传播,但这些浏览器都是从 window 对象开始捕获事件的。
由于老版本浏览器不支持,很少有人使用事件捕获。建议使用事件冒泡。有特殊情况再使用捕获。
DOM2 级事件流
“DOM2 级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。如图所示:

- 捕获阶段:实际目标(
<div>
元素)在捕获阶段不会接收事件,意思是事件从document->html->body
(1、2、3) - 目标阶段:事件在目标元素上发生。但事件处理被看作是冒泡阶段的一部分。
- 冒泡阶段:从目标元素开始处理事件,一直传播到文档。也就是
div->body->html->document
(4、5、6、7)
注意:
- “DOM2 级事件”规范明确要求捕获阶段不会涉及实际目标的事件,但 IE9、Chrome、Firefox、Safari 和 Opera9.5 及更高版本都会在捕获阶段触发实际目标上的事件。结果,目标对象上的事件就会执行两次!
- 并非所有的事件都会有冒泡阶段。但所有的事件都会经过捕获阶段和处于目标阶段。eg:跳过冒泡阶段的事件:获得输入焦点的 focus 事件和失去输入焦点的 blur 事件