一、DOM 事件流的三个阶段
一个完整的 DOM 事件流,严格来说分为三个阶段:
1. 捕获阶段 (Capture Phase)
- 方向:由外向内,从 window -> document -> -> -> ... -> 目标元素的父元素。
- 目的:让外层元素有机会在内层元素之前“拦截”并处理事件。
- 工作机制:浏览器会从最外层的祖先元素开始,检查它是否注册了捕获阶段的事件监听器。如果有,就执行它,然后继续向内层元素移动,重复此过程,直到到达事件的目标元素。
- 默认行为:默认情况下,我们用 addEventListener 绑定的事件监听器是不会在捕获阶段执行的。
2. 目标阶段 (Target Phase)
- 方向:事件到达真正被触发的那个元素(即事件目标,event.target)。
- 工作机制:浏览器执行注册在该目标元素上的事件监听器。
- 一个微妙的点:在目标阶段,事件并没有一个明确的“方向”。你可以认为它是捕获阶段的终点,也是冒泡阶段的起点。在这个阶段触发的监听器,无论是注册为捕获还是冒泡,都会被执行。
3. 冒泡阶段 (Bubbling Phase)
-
方向:由内向外,从事件目标开始,逐级向父元素传播,直到 window 对象。
-
目的:让内层元素的事件,能够被外层元素“感知”到。这是最常用、最符合直觉的阶段。
-
工作机制:从目标元素开始,浏览器检查它是否注册了冒泡阶段的事件监听器。如果有,就执行它,然后移动到其父元素,重复此过程,直到最外层的祖先元素。
-
默认行为:我们平时用 addEventListener(type, listener) 绑定的事件,默认就是在冒泡阶段执行的。
事件流的控制与应用
1. 阻止传播
-
event.stopPropagation() :
- 作用:在当前阶段执行完所有监听器后,阻止事件继续传播到下一个元素(无论是捕获阶段的“向下”,还是冒泡阶段的“向上”)。
- 比喻:军官收到士兵的战报后,决定“此事到此为止,不必再上报给将军了”。
-
event.stopImmediatePropagation() :
- 作用:一个更“霸道”的版本。它不仅会阻止事件向下一个元素传播,还会阻止在当前元素上、注册在它之后的任何同类型事件监听器被执行。
- 比喻:军官收到战报后,不仅不上报,还命令手下其他传令兵“你们也都别动了,这事我一个人处理就行”。
2. 阻止默认行为
-
event.preventDefault() :
-
作用:阻止浏览器对该事件的默认行为。
-
例子:
- 阻止 a 标签的点击跳转。
- 阻止 form 的提交按钮刷新页面。
- 阻止复选框的点击选中/取消选中。
-
注意:它不会阻止事件的传播。
-
3. 事件委托 (Event Delegation)
- 这是事件冒泡阶段最重要、最强大的应用模式。
通过在父元素上监听事件,来统一处理所有子元素的事件。这极大地提升了性能,并能方便地为动态添加的子元素绑定事件。
addEventListener的用法
target.addEventListener(type, listener, options);
type 表示触发事件的类型 比如 click
listener 事件触发之后执行的回调函数。
listener如果是一个普通函数,普通函数中的
this会被显示(call)的绑定到触发事件的dom上 listener如果是一个箭头函数,箭头函数中的this会绑定到全局对象上(call修改this指向,对箭头函数没用)
这个回调函数默认接收一个event参数
这个 event 对象包含了关于该事件的所有信息,比如:
- event.target: **真正触发**事件的那个最深层的元素。
- event.currentTarget: **当前正在处理**事件的元素(即你调用 addEventListener 的那个 target)。
- event.preventDefault(): 阻止事件的默认行为。
- event.stopPropagation(): 阻止事件继续在 DOM 树中传播(冒泡或捕获)。
- event.clientX, event.clientY: 鼠标事件的坐标。
- event.key: 键盘事件的按键值。
options (对象,可选) 或 useCapture (布尔值,可选)
这个参数用于配置监听器的行为,非常重要。你可以传入一个布尔值,或者一个更灵活的选项对象。
-
useCapture (布尔值) - 历史遗留用法
- false (默认值): listener 在冒泡阶段执行。
- true: listener 在捕获阶段执行。
-
options (对象) - 现代推荐用法
这个对象可以包含以下属性:-
capture: boolean:
- 与 useCapture 完全相同。true 表示在捕获阶段监听。
-
once: boolean:
- 如果为 true,表示这个 listener 最多只会被触发一次。触发后,它会自动被移除。
- 非常有用!避免了手动调用 removeEventListener 的麻烦。
- 示例:一个“欢迎”动画,只需要在页面首次加载时播放一次。
-
passive: boolean:
- 这是一个性能优化选项,主要用于触摸 (touch) 和滚轮 (wheel) 事件。
- 当你设置为 true 时,你等于在向浏览器承诺:“
**我的这个 listener 函数内部,绝对不会调用 event.preventDefault() 来阻止默认行为(比如滚动)。** ” - 好处:浏览器收到这个承诺后,就可以不必等待你的 listener 执行完毕,而是可以立即开始处理滚动,从而让页面滚动变得更加流畅,减少卡顿。对于提升移动端的滚动性能至关重要。
-
signal: AbortSignal:
- 用于中止/移除事件监听器。它与 AbortController API 配合使用。
-