JavaScript 事件系统
要点一: 事件系统的三大角色 —— 事件对象(存储状态)、事件源(被操作对象)、监听函数(回调处理)
- 事件最早在 IE3 和 Netscape Navigator 2 中出现,用于分担服务器运算负载
- IE4/Netscape4 时代出现了两种对立的事件流:IE 的冒泡流 vs Netscape 的捕获流
- DOM2 级规范统一了事件模型,形成捕获阶段 → 目标阶段 → 冒泡阶段的三阶段事件流
addEventListener第三个参数useCapture决定在捕获/冒泡阶段触发,默认false(冒泡)stopPropagation()只阻止向其他元素传播,不阻止当前元素的其他监听器stopImmediatePropagation()彻底阻止传播,包括当前元素的后续监听器- 事件代理(委托)利用冒泡特性,将子元素监听统一挂载到父节点,性能更优
事件系统概述
JavaScript 和 HTML 之间的交互通过事件实现。事件是文档或浏览器窗口中发生的特定交互瞬间,通过侦听器(处理程序)监听并执行相应代码。
三大核心角色
| 角色 | 作用 | 示例 |
|---|---|---|
| 事件对象 | 存储事件的状态信息(位置、类型、目标等) | event.target, event.clientX |
| 事件源对象 | 当前被操作的对象 | 元素节点、document、window、XMLHttpRequest |
| 事件监听函数 | 事件触发时执行的回调函数 | function(e) { ... } |
通俗类比:事件源相当于"当事人",监听函数相当于"监护人",事件对象相当于"事故详情"。当事人出事了,详情记录在案,监护人据此做出反应。
历史发展
事件最早在 IE3 和 Netscape Navigator 2 中出现,作为分担服务器运算负载的手段。到 IE4 和 Navigator4 发布时,两种浏览器提供了相似但不同的 API,并存了多个版本。直到 DOM2 级规范才开始以逻辑方式标准化 DOM 事件。
IE9、Firefox、Opera、Safari 和 Chrome 均已实现"DOM2 级事件"核心模块,IE8 是最后一个使用专有事件系统的主要浏览器。
浏览器事件系统相对复杂:尽管主流浏览器都实现了 DOM2 级事件,但规范本身并未涵盖所有事件类型;BOM 事件与 DOM 事件的关系长期缺乏明确规范(HTML5 后来给出了详细说明);DOM3 级的出现使事件 API 更加繁琐。但核心概念是必须理解的。
事件传播
浏览器发展到第四代(IE4 及 Netscape Communicator4)时,团队遇到了一个关键问题:点击目标元素时,同时也点击了其容器,甚至整个页面。如果这些元素都绑定了点击事件,执行顺序应该是怎样的?

事件流描述页面接收事件的顺序。IE 和 Netscape 团队提出了几乎完全相反的概念:
- IE 事件流:事件冒泡流(由内及外)
- Netscape 事件流:事件捕获流(由外及内)
事件冒泡 vs 事件捕获
| 模式 | 传播方向 | 描述 |
|---|---|---|
| 事件冒泡 | 由内及外 | 最具体的元素先接收,逐级向上传播到不具体的节点(document) |
| 事件捕获 | 由外及内 | 不具体的节点先接收,最具体的节点最后接收 |

所有现代浏览器都支持事件冒泡,但实现略有差异:
- IE5.5 及更早版本跳过
html元素(body 直接到 document) - IE9+、Firefox、Chrome、Safari 将事件一直冒泡到 window 对象
IE9+、Firefox、Chrome、Opera、Safari 均支持事件捕获。尽管 DOM 标准要求事件从 document 开始传播,但这些浏览器都是从 window 对象开始捕获。
由于老版本浏览器不支持事件捕获,实际开发中建议优先使用事件冒泡,仅在特殊需要时使用捕获。
DOM2 级事件流
"DOM2 级事件"规定的事件流包括三个阶段:

- 捕获阶段:事件从
document → html → body向下传播,不涉及实际目标 - 目标阶段:事件在目标元素上发生,事件处理被视为冒泡阶段的一部分
- 冒泡阶段:事件从目标元素向上传播到文档(
div → body → html → document)
- DOM2 级规范明确要求捕获阶段不涉及实际目标,但 IE9+、Chrome、Firefox、Safari、Opera9.5+ 都会在捕获阶段触发目标上的事件,导致目标元素事件执行两次
- 并非所有事件都有冒泡阶段,但所有事件都会经过捕获和目标阶段。例如
focus、blur事件跳过冒泡阶段