一、事件流模型
1. 事件流简介
事件流描述 DOM 事件 在 DOM 树中传播的完整过程,包含三个阶段:
2. 事件流的三大阶段
(1)捕获阶段(Capturing Phase)
- 事件从 window 向 目标元素 传播
- 从最外层祖先元素向内传播,直到目标元素
- 通过
addEventListener 的第三个参数为 true 启用
(2)目标阶段(Target Phase)
- 事件到达实际触发事件的元素
- 执行目标元素上绑定的所有事件监听器
- 此阶段不分捕获和冒泡,按添加顺序执行
(3)冒泡阶段(Bubbling Phase)
- 事件从 目标元素 向 window 传播
- 从目标元素向外传播到最外层祖先元素
- 默认阶段,大多数事件在此阶段触发
3. 完整事件流顺序
1 2 3 4 5 6 7 8 9 10 11
| 用户交互触发事件 ↓ 创建事件对象(包含所有事件信息) ↓ 捕获阶段:window → document → body → 父元素 → 目标元素 ↓(依次执行每个元素上的捕获回调) 目标阶段:在目标元素上执行所有监听器 ↓(按添加顺序执行,不分捕获/冒泡) 冒泡阶段:目标元素 → 父元素 → body → document → window ↓(依次执行每个元素上的冒泡回调) 事件结束
|
二、事件监听与注册
1. 事件监听方法
1 2 3 4 5 6 7 8 9
| element.addEventListener("click", callback, true);
element.addEventListener("click", callback, false); element.addEventListener("click", callback);
element.removeEventListener("click", callback, useCapture);
|
2. 事件传播控制
1 2 3 4
| element.addEventListener("click", function (event) { event.stopPropagation(); event.stopImmediatePropagation(); });
|
三、事件对象(Event Object)
1. 事件对象基础
- 事件触发时浏览器自动创建
- 作为第一个参数传递给所有事件监听器
- 包含事件的完整上下文信息
- 在整个事件流中传递的是同一个对象
2. 核心属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| event.target; event.currentTarget; event.relatedTarget;
event.type; event.timeStamp; event.bubbles; event.cancelable; event.defaultPrevented;
event.eventPhase;
|
3. 事件对象方法
1 2 3
| event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation();
|
四、不同类型事件的特殊属性
1. 鼠标事件(MouseEvent)
1 2 3 4 5 6 7 8 9 10 11 12
| event.clientX, event.clientY; event.pageX, event.pageY; event.screenX, event.screenY; event.offsetX, event.offsetY;
event.button; event.buttons;
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey;
|
2. 键盘事件(KeyboardEvent)
1 2 3 4 5
| event.key; event.code; event.keyCode; event.altKey, event.ctrlKey, event.shiftKey, event.metaKey; event.repeat;
|
3. 拖拽事件(DragEvent)
1 2
| event.dataTransfer; event.dataTransfer.files;
|
五、事件执行顺序详解
1. 同一元素的多个监听器
1 2 3 4 5
| element.addEventListener("click", () => console.log("第一个")); element.addEventListener("click", () => console.log("第二个"));
|
2. 完整事件流示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div id="outer"> <div id="inner"> <button id="btn">Click</button> </div> </div>
<script> outer.addEventListener("click", () => console.log("outer 捕获"), true); inner.addEventListener("click", () => console.log("inner 捕获"), true);
btn.addEventListener("click", () => console.log("btn 目标1")); btn.addEventListener("click", () => console.log("btn 目标2"));
inner.addEventListener("click", () => console.log("inner 冒泡"), false); outer.addEventListener("click", () => console.log("outer 冒泡"), false); </script>
|
点击输出:
1 2 3 4 5 6
| outer 捕获 inner 捕获 btn 目标1 btn 目标2 inner 冒泡 outer 冒泡
|
六、事件委托(Event Delegation)
1. 事件委托原理
利用事件冒泡,在父元素上处理子元素的事件
2. 事件委托优势
- 减少事件监听器数量
- 动态添加的子元素自动拥有事件处理
- 更好的性能表现
3. 事件委托实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <ul id="itemList"> <li data-id="1">项目1</li> <li data-id="2">项目2</li> <li data-id="3">项目3</li> </ul>
<script> document .getElementById("itemList") .addEventListener("click", function (event) { if (event.target.tagName === "LI") { const itemId = event.target.dataset.id; console.log("点击了项目:", itemId); } }); </script>
|
七、自定义事件
1. 创建自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const customEvent = new CustomEvent("myEvent", { detail: { message: "自定义数据", timestamp: Date.now(), }, bubbles: true, cancelable: true, });
element.dispatchEvent(customEvent);
element.addEventListener("myEvent", function (event) { console.log("自定义事件数据:", event.detail); });
|
八、事件对象生命周期
1. 创建阶段
1 2 3 4 5 6 7
| const event = new MouseEvent("click", { target: buttonElement, clientX: 100, clientY: 200, });
|
2. 传播过程中的变化
1 2 3 4 5 6 7 8 9
| event.currentTarget; event.eventPhase;
event.target; event.type; event.timeStamp;
|
九、实用模式和最佳实践
1. 事件调试工具
1 2 3 4 5 6 7 8
| function debugEvent(event) { console.group("事件调试"); console.log("类型:", event.type); console.log("目标:", event.target); console.log("当前目标:", event.currentTarget); console.log("阶段:", event.eventPhase); console.groupEnd(); }
|
2. 事件委托最佳实践
1 2 3 4 5 6 7
| document.getElementById("list").addEventListener("click", function (event) { const listItem = event.target.closest("li.item"); if (listItem) { console.log("点击项目:", listItem.dataset.id); } });
|
3. 性能优化
1 2 3 4 5
| element.addEventListener("touchstart", handler, { passive: true });
element.addEventListener("click", handler, { once: true });
|
十、核心要点总结
1. 事件流要点
- 三个阶段:捕获 → 目标 → 冒泡
- 执行顺序:每个元素上的回调按添加顺序依次执行
- 传播控制:
stopPropagation() 和 stopImmediatePropagation()
2. 事件对象要点
- 统一对象:整个事件流使用同一个事件对象
- 目标区分:
target(实际触发)vs currentTarget(当前处理)
- 行为控制:阻止默认行为、停止传播等方法
3. 最佳实践要点
- 事件委托:处理动态内容,减少事件绑定
- 性能优化:使用 passive、once 等选项
- 错误处理:使用 closest 等方法提高容错性
4. 重要概念
- 事件对象在触发时创建,传播过程中部分属性被修改
- 同一元素的多个监听器按添加顺序执行
- 事件委托利用冒泡机制简化事件处理
- 理解事件流顺序对于编写可预测的代码至关重要