# 事件-Event

Event 接口表示在 DOM 中出现的事件。

一些事件是由用户触发的,例如鼠标或键盘事件;而其他事件常由 API 生成,例如指示动画已经完成运行的事件,视频已被暂停等等。事件也可以通过脚本代码触发,例如对元素调用 HTMLElement.click() 方法,或者定义一些自定义事件,再使用 EventTarget.dispatchEvent() 方法将自定义事件派发往指定的目标(target)。

有许多不同类型的事件,其中一些使用基于 Event 主接口的二次接口。Event 本身包含适用于所有事件的属性和方法。

# 浏览器事件简介

事件 是某事发生的信号。所有的 DOM 节点都生成这样的信号(但事件不仅限于 DOM)。

# 事件处理程序

为了对事件作出响应,我们可以分配一个 处理程序(handler)—— 一个在事件发生时运行的函数。

处理程序是在发生用户行为(action)时运行 JavaScript 代码的一种方式。

<script>
  function countRabbits() {
    for (let i = 1; i <= 3; i++) {
      alert("Rabbit number " + i);
    }
  }
</script>

<input 
type="button" 
onclick="countRabbits()" 
value="Count rabbits!" 
/>

我们知道,HTML 特性名是大小写不敏感的,所以 ONCLICK 和 onClick 以及 onCLICK 都一样可以运行。但是特性通常是小写的:onclick。

我们可以使用 DOM 属性(property)on<event> 来分配处理程序。而不是必须把事件处理程序在 HTML 中进行绑定。

<input type="button" id="button" value="Button" />
<script>
  button.onclick = function () {
    alert("Click!");
  };
</script>

因为这里只有一个 onclick 属性,所以我们无法分配更多事件处理程序。

# 多个处理程序

上述分配处理程序的方式的根本问题是 —— 我们不能为一个事件分配多个处理程序。

假设,在我们点击了一个按钮时,我们代码中的一部分想要高亮显示这个按钮,另一部分则想要显示一条消息。

我们想为此事件分配两个处理程序。但是,新的 DOM 属性将覆盖现有的 DOM 属性:

input.onclick = function () {
  alert(1);
};
// ...
input.onclick = function () {
  alert(2);
}; // 替换了前一个处理程序

Web 标准的开发者很早就了解到了这一点,并提出了一种使用特殊方法 addEventListener 和 removeEventListener 来管理处理程序的替代方法。它们没有这样的问题。

添加处理程序的语法:

element.addEventListener(event, listener[, options]);

要移除处理程序,可以使用 removeEventListener:

element.removeEventListener(event, listener[, options]);
function handler() {
  alert("Thanks!");
}

input.addEventListener("click", handler);
// handler 指向同一个函数
input.removeEventListener("click", handler);

# 事件模型

浏览器的事件模型,就是通过监听函数对事件作出反应。这是事件驱动编程模式的主要编程方式。

# 事件的传播

一个事件发生后会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  1. 第一阶段:从 window 对象传导到目标节点,称为捕获阶段 capture phase
  2. 第二阶段:在目标节点上触发,称为目标阶段 target phase
  3. 第三阶段:从目标阶段传导回 window 对象,称为冒泡阶段。 bubbling phase

这三阶段的传播模型,使得同一事件会在多个节点上触发。

注意浏览器总是假定事件的目标节点,是事件发生位置嵌套最深的那个节点,所以最深的那个节点的捕获和冒泡都会显示为 target 阶段,因此这个节点也会触发两次。

# 事件的代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,利用 event.target 定位到触发事件的子元素,由父节点的监听函数同意处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

  • event.target
var ul = document.querySelector("ul");
ul.addEventListener("click", function (event) {
  if (event.target.tagName.toLowerCase() === "li") {
    // some code
  }
});

如果希望事件到某个节点为止,不在传播,可以使用事件对象的 stopPropagation()方法。这个方法只会阻止事件的传播,不会阻止事件触发节点的其他事件对于的监听函数。

// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener(
  "click",
  function (event) {
    event.stopPropagation();
  },
  true
);
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener(
  "click",
  function (event) {
    event.stopPropagation();
  },
  false
);

# 事件对象 Event

事件发生以后,会产生一个 Event 事件对象,作为参数传递给监听函数。浏览器原生提供一个 Event 对象,所有的事件都是这个对象(构造函数)的实例,继承了 Event.prototype 原型对象。

Event 对象的实例属性
Event.cancelBubble 返回一个布尔值,如果设为 true,可以阻止事件冒泡
Event.currentTarget 返回事件当前正在通过的节点(捕获,冒泡)
Event.target 返回原始触发事件的那个节点
Event.type 返回一个字符串,表示事件类
Event.timeStamp 返回一个毫秒时间戳,表示事件发生的事件,相对于网页加载成功时
Event 对象的实例方法
Event.preventDefault() 取消浏览器对当前事件的默认行为,不会阻止其他事件的传播
Event.stopPropagation() 阻止当前事件在 DOM 中继续传播