JS中的Event Propagation — 事件传播机制

2023/02/26

一、介绍

捕获阶段(Capturing Phase):从window对象向下传播到目标元素

目标阶段(Target Phase):在事件目标上触发

冒泡阶段(Bubbling Phase):从目标元素向上传播回window对象

// 捕获阶段触发(第三个参数为true)
element.addEventListener('click', handler, true);

// 冒泡阶段触发(默认false)
element.addEventListener('click', handler, false); 
// 或
element.addEventListener('click', handler);

// 适时移除
element.removeEventListener('click', handler);

二、常见事件的冒泡情况

1.不会冒泡事件

类型常见事件说明
焦点事件focus, blur使用 focusin/focusout 代替可冒泡版本
加载类事件load, unload, beforeunload, DOMContentLoaded这些事件一般只发生在特定元素(如 window, document)
表单类事件resize, scroll, abort, error作用于元素自身,不具备冒泡意义
鼠标事件mouseenter、mouseleave
媒体事件play, pause, playing, ended 等通常不冒泡
剪贴板事件copy, cut, paste (某些浏览器冒泡不一致)

2.会冒泡事件

类型常见事件
鼠标事件click, dblclick, mousedown, mouseup, contextmenu
表单事件input, change, focusin, focusout (注意区别于 focus 和 blur)
键盘事件keydown, keyup, keypress
触摸事件touchstart, touchend, touchmove
拖拽事件dragstart, dragend, dragenter, dragleave, dragover, drop
其他submit, reset, pointerdown, pointerup, pointermove, wheel

三、相关操作

1.判断事件是否冒泡

console.log(Event.prototype.bubbles); // false,默认值

2.阻止事件冒泡

// 方法1:event.stopPropagation()
document.getElementById('inner').addEventListener('click', (e) => {
  console.log('Inner clicked');
  e.stopPropagation(); // 阻止向上冒泡
});

// 方法2:return false (jQuery特有方式)
$('#inner').on('click', () => {
  console.log('Inner clicked');
  return false; // 同时阻止冒泡和默认行为
});

// 方法3:event.stopImmediatePropagation()
document.getElementById('inner').addEventListener('click', (e) => {
  console.log('First handler');
  e.stopImmediatePropagation(); // 阻止其他监听器和冒泡
});

3.访问事件的目标和当前目标

<div id="parent">
  <button id="child">Click Me</button>
</div>

<script>
  const parent = document.getElementById("parent");

  parent.addEventListener("click", function (event) {
    console.log('currentTarget:', e.currentTarget); // 当前绑定事件的元素(监听器所在的),即parent
    console.log('target:', e.target); // 实际触发事件的元素(真正点击的),即child
  });
</script>

4.通过创建事件手动设置是否冒泡

const customEvent = new Event('my-event', { bubbles: true });

四、案例题目

<div id="outer">
  <div id="inner">
    <button id="btn">Click Me</button>
  </div>
</div>

<script>
  const outer = document.getElementById("outer");
  const inner = document.getElementById("inner");
  const btn = document.getElementById("btn");

  outer.addEventListener("click", () => {
    console.log("outer capture");
  }, true);

  outer.addEventListener("click", () => {
    console.log("outer bubble");
  });

  inner.addEventListener("click", (e) => {
    console.log("inner capture");
  }, true);

  inner.addEventListener("click", (e) => {
    console.log("inner bubble");
    e.stopPropagation();
  });

  btn.addEventListener("click", () => {
    console.log("btn click");
  });
</script>

结果:
outer capture  // 捕获阶段
inner capture  // 捕获阶段
btn click  // 目标阶段
inner bubble  // 冒泡阶段,因为阻止冒泡了,所以outer bubble不打印