Skip to content

事件

事件模型

事件的本质是程序的各个组成部分之间的一种通信方式,也是异步编程的一种实现,DOM 支持大量的事件。

DOM 节点的事件操作(监听和触发) —— EventTarget 接口

DOM 节点的事件操作(监听和触发),都定义在 EventTarget 接口。

所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如XMLHttpRequestAudioNodeAudioContext)也部署了这个接口

主要提供三个实例方法:

  • addEventListener():绑定事件的监听函数

  • removeEventListener():移除事件的监听函数

  • dispatchEvent():触发事件

addEventListener

js
target.addEventListener(type, listener[, useCapture]);

// type:事件名称,大小写敏感。

// listener:监听函数。事件发生时,会调用该监听函数。

// useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。

// 第三个参数除了布尔值useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。

// capture:布尔值,如果设为true,表示监听函数在捕获阶段触发,默认为false,在冒泡阶段触发。

// once:布尔值,如果设为true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false。

// passive:布尔值,设为true时,表示禁止监听函数调用preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false。

// signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。
js
element.addEventListener(
  "click",
  function (event) {
    // 只执行一次的代码
  },
  { once: true }
);

removeEventListener

js
div.addEventListener("click", listener, false);
div.removeEventListener("click", listener, false);

dispatchEvent

在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为 false,否则为 true

js
target.dispatchEvent(event);

第一个参数是 Event 对象的实例

js
para.addEventListener("click", hello, false);
var event = new Event("click");
para.dispatchEvent(event);

监听函数

浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。

事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。

这是事件驱动编程模式(event-driven)的主要编程方式。

JavaScript 有三种方法,可以为事件绑定监听函数:

1、HTML 的on-属性

on加上事件名

违反了 HTML 和 JavaScript 代码相分离的原则

html
<body onload="doSomething()">
  <div onclick="console.log('触发事件')"></div>
</body>

2、元素节点的事件属性

同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。

js
window.onload = doSomething;

div.onclick = function (event) {
  console.log("触发事件");
};

3、EventTarget.addEventListener()

所有的 DOM 节点实例都有addEventListener方法,称为该节点定义事件的监听函数。

  • 同一个事件可以添加多个监听函数

  • 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数

  • 除了 DOM 节点,其他对象(比如 window、XMLHttpRequest 等)也有这个接口,等于是整个 JavaScript 统一的监听函数接口。

js
window.addEventListener("load", doSomething, false);

this 指向

监听函数内部的 this 指向触发事件的那个元素节点

事件的传播

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

  • 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)

  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)

  • 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)

js
<div>
  <p>点击</p>
</div>;

var phases = { 1: "capture", 2: "target", 3: "bubble" };
var div = document.querySelector("div");
var p = document.querySelector("p");
div.addEventListener("click", callback, true);
p.addEventListener("click", callback, true);
div.addEventListener("click", callback, false);
p.addEventListener("click", callback, false);
function callback(event) {
  var tag = event.currentTarget.tagName;
  var phase = phases[event.eventPhase];
  console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
} //点击以后的结果; 
// Tag: 'DIV'. EventPhase: 'capture' // Tag: 'P'. EventPhase:("target"); 
// Tag: 'P'. EventPhase: 'target' // Tag: 'DIV'. EventPhase: 'bubble'

浏览器总是假定 click 事件的目标节点,就是点击位置嵌套最深的那个节点(本例是<div>节点里面的<p>节点)。所以,<p>节点的捕获阶段和冒泡阶段,都会显示为 target 阶段。

事件传播的最上层对象是window,接着依次是documenthtmldocument.documentElement)和bodydocument.body)。也就是说,上例的事件传播顺序,在捕获阶段依次为windowdocumenthtmlbodydivp,在冒泡阶段依次为pdivbodyhtmldocumentwindow

事件代理

事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父结点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

js
var ul = document.querySelector("ul");

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

如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法

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

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

stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数。也就是说,不是彻底取消click事件。

js
p.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log(1);
});

p.addEventListener("click", function (event) {
  // 会触发
  console.log(2);
});

// p元素绑定了两个click事件的监听函数。stopPropagation方法只能阻止这个事件的传播,不能取消这个事件,因此,第二个监听函数会触发。输出结果会先是1,然后是2。

如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法

js
p.addEventListener("click", function (event) {
  event.stopImmediatePropagation();
  console.log(1);
});

p.addEventListener("click", function (event) {
  // 不会被触发
  console.log(2);
});

Event 对象

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

Event对象本身就是一个构造函数,可以用来生成新的实例

js
event = new Event(type, options);

接收两个参数,type是字符串,表示事件的名称;第二个参数options是一个对象,表示事件对象的配置。

该配置主要有下面两个属性:

  • bubbles:布尔值,可选,默认为 false,表示事件对象是否冒泡

  • cancelable:布尔值,可选,默认为 false,表示事件是否可以被取消,即能否用Event.preventDefault()取消这个事件。一旦事件被取消,就好像从来没有发生过,不会触发浏览器对该事件的默认行为。

实例属性

  • Event.eventPhase:返回一个整数常量,表示事件目前所处的阶段,该属性只读
  • 0,事件目前没有发生

  • 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中

  • 2,事件到达目标节点,即Event.target属性指向的那个节点

  • 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中

  • Event.currentTaget, Event.target

事件发生以后,会经过捕获和冒泡两个阶段,依次通过多个 DOM 节点。

因此,任意事件都有两个与事件相关的节点,一个是原始触发节点(Event.target),另一个是事件当前正在通过的节点(Event.currentTarget)

前者通常是后者的后代节点

  • Event.currentTarget 属性返回事件当前所在的节点,即事件当前正在通过的节点,也就是当前正在执行的监听函数所在的节点,随着事件的传播,这个属性的值会变

  • Event.target 属性返回原始触发事件的那个节点,即事件最初发生的节点,这个属性不会随着事件的传播而改变

  • Event.isTrusted:返回布尔值,表示该事件是否由真实的用户行为产生

    比如,用户点击链接会产生一个 click 事件,该事件是用户产生的;Event 构造函数生成的事件,则是脚本产生的。

实例方法

  • Event.preventDefault():取消浏览器对当前事件的默认行为。

比如点击链接,浏览器默认会跳转到另一个页面,使用这个方法就不会跳转了

  • Event.stopPropagation():阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但不包括在当前节点上其他事件监听函数

  • Event.stopImmediatePropagation():阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。

  • Event.composedPath():返回一个数组,成员是事件的最底层节点和依次冒泡经过的上层节点

js
// HTML 代码如下
// <div>
//   <p>Hello</p>
// </div>
var div = document.querySelector("div");
var p = document.querySelector("p");

div.addEventListener(
  "click",
  function (e) {
    console.log(e.composedPath());
  },
  false
);
// [p, div, body, html, document, Window]

一些事件

  • 鼠标事件

  • 键盘事件

  • 进度事件

  • 表单事件

  • 触摸事件

  • 拖拉事件

  • 其他常见事件

  • 资源事件

  • session 历史事件

  • 网页状态事件

  • 窗口事件

  • 剪贴板事件

  • 焦点事件

  • CustomEvent 接口

CustomEvent 接口用于生成自定义事件实例。

浏览器的预定义事件,虽然可以手动生成,但往往不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,就可以使用 CustomEvent 接口生成的自定义事件对象

js
// 手动定义了build事件
var event = new CustomEvent("build", { detail: "hello" });

function eventHandler(e) {
  console.log(e.detail);
}

document.body.addEventListener("build", function (e) {
  console.log(e.detail);
});

document.body.dispatchEvent(event);

GlobalEventHandlers 接口

指定事件的回调函数,推荐使用的方法是元素的addEventListener方法

js
div.addEventListener("click", clickHandler, false);

除此之外,还有一种方法可以直接指定事件的回调函数

js
div.onclick = clickHandler;

这个接口由 GlobalEventHandlers 接口提供的。

它的优点是使用比较方便,缺点是只能为每个事件指定一个回调函数,并且无法指定事件触发的阶段。

HTMLElement、Document 和 Window 都继承了这个接口,也就是说,各种 HTML 元素、document 对象、window 对象上面都可以使用 GlobalEventHandlers 接口提供的属性