在目标冒泡和捕获事件顺序

Bubbling and Capturing events order at target

目标阶段的冒泡和捕获事件顺序是否在任何地方定义?

几乎任何地方你都会发现,首先是捕获阶段,然后是冒泡阶段。 Some 消息来源还提到目标阶段是一个单独的阶段。

但是,提及目标阶段的消息来源并未指定此阶段的事件顺序。我假设注册为捕获的事件将先于注册为冒泡的事件。 事实并非总是如此!

事实上——根据我的小实验结果——目标阶段的事件处理程序执行完全取决于它们的注册顺序。

const extBox = document.querySelector('.external-box');
const intBox = document.querySelector('.internal-box');
const par = document.querySelector('p');

extBox.addEventListener('click', bubblingEvent, false);
intBox.addEventListener('click', bubblingEvent, false);
par.addEventListener('click', bubblingEvent, false);

par.addEventListener('click', capturingEvent, true);
extBox.addEventListener('click', capturingEvent, true);
intBox.addEventListener('click', capturingEvent, true);      
      
function bubblingEvent(event) {
  console.log(event.currentTarget.className + ': bubbling event, ' + getPhaseStr(event.eventPhase));
}
      
function capturingEvent(event) {
  console.log(event.currentTarget.className + ': capturing event, ' + getPhaseStr(event.eventPhase));
}

function getPhaseStr (eventPhase) {
  let eventPhaseStr;

  switch (eventPhase) {
    case 0:
      eventPhaseStr = 'NONE';
      break;
    case 1:
      eventPhaseStr = 'CAPTURING_PHASE';
      break;
    case 2:
      eventPhaseStr = 'AT_TARGET';
      break;
    case 3:
      eventPhaseStr = 'BUBBLING_PHASE';
      break;
    default:
      eventPhaseStr = 'ERROR';
  }

  return eventPhaseStr;
}
* {
  box-sizing: border-box;
}

.external-box, .internal-box {
  width: 50%;
  margin: 1rem;
  padding: 1rem;
}

.external-box {
  background-color: aquamarine;
}

.internal-box {
  background-color: blueviolet;
}

p {
  background-color: cornsilk;
  padding: 1rem;
}
<div class='external-box'>external-box
  <div class='internal-box'>internal-box
    <p class='par'>paragraph</p>
  </div>
</div>

在上面的示例中我们可以看到,在目标阶段期间,为冒泡阶段添加的事件在捕获阶段之前执行。 这是因为我先为冒泡阶段注册了事件。如果我先为捕获阶段注册事件,然后为冒泡阶段注册事件,则顺序将为 "correct".

我的问题再一次:目标阶段的事件顺序是否在任何地方定义?

你是对的。当一个事件监听器附加到一个元素上,并且该元素是目标时,事件监听器是设置为在捕获期间激活还是在冒泡期间激活都没有关系。官方规范对其进行了描述here.

  1. For each listener in listeners, whose removed is false:

  2. If phase is "capturing" and listener’s capture is false, then continue.

  3. If phase is "bubbling" and listener’s capture is true, then continue.

  4. (eventually invoke attached listener)

处于目标 时,阶段既不捕获也不冒泡,如 getPhaseStr 函数解析 eventPhase 的方式所示。所以 3. continue4. continue 都没有被激活,所以监听器最终被调用,按照监听器被附加的顺序。 (监听器顺序保存在 "event listener list",一个附加到特定元素的监听器列表)

b1.addEventListener('click', () => console.log('bubbling'));
b1.addEventListener('click', () => console.log('capturing'), true);

b2.addEventListener('click', () => console.log('capturing'), true);
b2.addEventListener('click', () => console.log('bubbling'));
<button id="b1">click</button>
<button id="b2">click</button>