/ html

DOM事件流

规范

"DOM2级事件"规定的事件流包括三个阶段:

  • 事件捕获阶段 (Evnet Capturing Phase)
  • 处于目标阶段 (Target Phase)
  • 事件冒泡阶段 (Event Bubbling Phase)

说明

以下面这个页面为例,

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
  div{
    background: lime;
  }
  </style>
</head>
<body>
  <div id="wrap">
    <button id="btn">点我</button>
  </div>
  <script src="index.js"></script>
</body>
</html>

JS:

(function() {
	var $ = function (obj) {
    return document.querySelector(obj);
  };

  var showID1 = function (e) {
    if(this.id) {
      console.log("捕获:事件传递到了" + this.id + ",目标节点是" + e.target.id);
    } else {
      console.log("捕获:事件传递到了" + this + ",目标节点是" + e.target.id);
    }
    this.removeEventListener("click", showID1, true);
  };

  var showID2 = function (e) {
    if(this.id) {
			console.log("冒泡:事件传播到了" + this.id + ",目标节点是" + e.target.id);
    } else {
      console.log("冒泡:事件传播到了" + this + ",目标节点是" + e.target.id);
    }
    this.removeEventListener("click", showID2, false);
  };

  window.addEventListener("click", showID1, true);
  document.addEventListener("click", showID1, true);
  $("html").addEventListener("click", showID1, true);
  $("body").addEventListener("click", showID1, true);
  $("#wrap").addEventListener("click", showID1, true);
  $("#btn").addEventListener("click", showID1, true);

  window.addEventListener("click", showID2, false);
  document.addEventListener("click", showID2, false);
  $("html").addEventListener("click", showID2, false);
  $("body").addEventListener("click", showID2, false);
  $("#wrap").addEventListener("click", showID2, false);
  $("#btn").addEventListener("click", showID2, false);
})();

理想

当你单击<button>元素时,"DOM2级事件"规范,会期望浏览器干这么些事(如下示意图):

  • document首先接收到click事件,然后顺着DOM树逐级向下传递事件 (也就是所谓的事件捕获)
  • 事件最终传递到目标节点 (目标阶段)
  • 最后事件由目标节点顺着DOM树逐级向上传播回document (事件冒泡)

DOM2级规范
需要留意的是目标节点(此处为<button>)在捕获阶段不会接收到事件,这意味着在捕获阶段,事件的传递从document<html>再到<body>最后到<div id="wrap">后就停止了;然后就是处于目标阶段,而在事件处理中目标阶段被看做冒泡阶段的一部分。

现实

当然现实与期望还是会有些出入的,尽管"DOM2级事件"规范明确要求捕获阶段不会涉及到事件目标,但"现代浏览器"(IE9+,Safari,Chrome,Firefox和Opera9.5+)都会在捕获阶段都会触发事件对象上的事件,结果就是有两个机会在目标节点上面操作事件;此外这些浏览器都是从window对象开始捕获事件的。

如下实际中浏览器发生的DOM事件流 (同样单击<button>元素)
DOM2级规范
DOM2级规范

证实

对上面的JS做些改动,更换下书写顺序:
JS:

(function() {
	var $ = function (obj) {
    return document.querySelector(obj);
  };

  var showID1 = function (e) {
    if(this.id) {
      console.log("捕获:事件传递到了" + this.id + ",目标节点是" + e.target.id);
    } else {
      console.log("捕获:事件传递到了" + this + ",目标节点是" + e.target.id);
    }
    this.removeEventListener("click", showID1, true);
  };

  var showID2 = function (e) {
    if(this.id) {
			console.log("冒泡:事件传播到了" + this.id + ",目标节点是" + e.target.id);
    } else {
      console.log("冒泡:事件传播到了" + this + ",目标节点是" + e.target.id);
    }
    this.removeEventListener("click", showID2, false);
  };

	window.addEventListener("click", showID2, false);
  window.addEventListener("click", showID1, true);

  document.addEventListener("click", showID2, false);
  document.addEventListener("click", showID1, true);

	$("html").addEventListener("click", showID2, false);
	$("html").addEventListener("click", showID1, true);

	$("body").addEventListener("click", showID2, false);
	$("body").addEventListener("click", showID1, true);

	$("#btn").addEventListener("click", showID2, false);
	$("#btn").addEventListener("click", showID1, true);

	$("#wrap").addEventListener("click", showID2, false);
	$("#wrap").addEventListener("click", showID1, true);
})();

点击<button id="btn">
DOM2级规范
点击<div id="wrap">
DOM2级规范
点击<html>
DOM2级规范

小结

当事件处于目标阶段时,事件触发顺序决定于事件处理程序的书写顺序

最后

不暴露你Low的一面,又怎能变优秀,So代码有问题(优化)或者关于DOM事件流的称述有不足之处,还望指出

参考资料:

  • 《javascript高级程序设计》
  • 《javascript权威指南》