事件简介

事件是在您正在编程的系统中发生的事件,系统会将这些事件通知您,以便您的代码可以对它们做出反应。

例如,如果用户点击网页上的按钮,您可能希望通过显示信息框来对该操作做出反应。在本文中,我们将讨论围绕事件的一些重要概念,并了解它们在浏览器中的工作原理。这不会是一项详尽的研究;只是您在此阶段需要了解的内容。

先决条件 对 HTML、CSS 和 JavaScript 初步知识 的基本了解。
目标 了解事件的基本理论、它们在浏览器中的工作原理以及事件在不同编程环境中的差异。

什么是事件?

事件是在您正在编程的系统中发生的事件——当事件发生时,系统会发出某种信号(或“触发”),并提供一种机制,以便在事件发生时自动执行某个操作(即运行某些代码)。事件在浏览器窗口内触发,并且往往与驻留在其中的特定项目相关联。这可能是一个单独的元素、一组元素、当前选项卡中加载的 HTML 文档或整个浏览器窗口。可以发生许多不同类型的事件。

例如

  • 用户选择、点击或将光标悬停在某个元素上。
  • 用户选择键盘上的某个键。
  • 用户调整浏览器窗口大小或关闭浏览器窗口。
  • 网页加载完成。
  • 提交表单。
  • 播放、暂停或结束视频。
  • 发生错误。

您可以从这里(以及浏览 MDN 的 事件参考)中了解到,可以触发 **很多** 事件。

要对事件做出反应,您需要向其附加一个 **事件处理程序**。这是一个代码块(通常是您作为程序员创建的 JavaScript 函数),当事件触发时运行。当定义此类代码块以响应事件运行时,我们说我们正在 **注册事件处理程序**。注意:事件处理程序有时被称为 **事件侦听器**——就我们的目的而言,它们几乎可以互换使用,尽管严格来说,它们是协同工作的。侦听器侦听事件的发生,而处理程序是在事件发生时运行的代码。

注意:Web 事件不是核心 JavaScript 语言的一部分——它们被定义为浏览器内置 API 的一部分。

一个示例:处理点击事件

在下面的示例中,我们在页面中有一个单独的 <button>

html
<button>Change color</button>

然后我们有一些 JavaScript。我们将在下一节中更详细地讨论这一点,但现在我们可以简单地说:它向按钮的 "click" 事件添加了一个事件处理程序,并且处理程序通过将页面背景设置为随机颜色来对事件做出反应

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

btn.addEventListener("click", () => {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
});

示例输出如下。尝试点击按钮

使用 addEventListener()

正如我们在上一个示例中看到的,可以触发事件的对象具有 addEventListener() 方法,这是添加事件处理程序的推荐机制。

让我们仔细看看上一个示例中的代码

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

btn.addEventListener("click", () => {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
});

HTML <button> 元素在用户点击按钮时会触发事件。因此它定义了一个 addEventListener() 函数,我们在这里调用它。我们传递了两个参数

  • 字符串 "click",表示我们想要监听点击事件。按钮可以触发许多其他事件,例如 "mouseover"(当用户将鼠标悬停在按钮上时)或 "keydown"(当用户按下某个键并且按钮处于焦点状态时)。
  • 当事件发生时要调用的函数。在我们的例子中,该函数生成一个随机的 RGB 颜色,并将页面的 background-color 设置为该颜色 <body>

将处理程序函数作为单独的命名函数是可以的,如下所示

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

function changeBackground() {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
}

btn.addEventListener("click", changeBackground);

监听其他事件

按钮元素可以触发许多不同的事件。让我们来试验一下。

首先,制作 random-color-addeventlistener.html 的本地副本,并在浏览器中打开它。它只是我们已经使用过的简单随机颜色示例的副本。现在尝试依次将 click 更改为以下不同的值,并观察示例中的结果

  • focusblur——当按钮获得焦点和失去焦点时,颜色会发生变化;尝试按 Tab 键将焦点放在按钮上,然后再次按 Tab 键将焦点移开。这些通常用于在表单字段获得焦点时显示有关填写表单字段的信息,或者如果表单字段填写了错误的值,则显示错误消息。
  • dblclick——仅当双击按钮时,颜色才会发生变化。
  • mouseovermouseout——分别在鼠标指针悬停在按钮上或指针移开按钮时,颜色会发生变化。

某些事件(例如 click)几乎可以在任何元素上使用。其他事件则更具体,并且仅在某些情况下有用:例如,play 事件仅在某些元素(例如 <video>)上可用。

移除侦听器

如果您使用 addEventListener() 添加了事件处理程序,则可以使用 removeEventListener() 方法将其再次移除。例如,这将移除 changeBackground() 事件处理程序

js
btn.removeEventListener("click", changeBackground);

还可以通过将 AbortSignal 传递给 addEventListener(),然后稍后在拥有 AbortSignal 的控制器上调用 abort() 来移除事件处理程序。例如,要添加一个可以使用 AbortSignal 移除的事件处理程序

js
const controller = new AbortController();

btn.addEventListener("click",
  () => {
    const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
    document.body.style.backgroundColor = rndCol;
  },
  { signal: controller.signal } // pass an AbortSignal to this handler
);

然后,上面代码创建的事件处理程序可以像这样移除

js
controller.abort(); // removes any/all event handlers associated with this controller

对于简单的小程序,清理旧的、未使用的事件处理程序不是必需的,但对于更大、更复杂程序,它可以提高效率。此外,移除事件处理程序的功能使您可以让同一个按钮在不同的情况下执行不同的操作:您只需添加或移除处理程序即可。

为单个事件添加多个侦听器

通过多次调用 addEventListener() 并提供不同的处理程序,您可以为单个事件提供多个处理程序

js
myElement.addEventListener("click", functionA);
myElement.addEventListener("click", functionB);

现在,当元素被点击时,这两个函数都会运行。

了解更多

addEventListener() 提供了其他强大的功能和选项。

这些超出了本文的范围,但如果您想阅读它们,请访问 addEventListener()removeEventListener() 参考页面。

其他事件监听器机制

我们建议您使用 addEventListener() 注册事件处理程序。它是功能最强大的方法,并且最适合扩展到更复杂的程序。但是,还有两种注册事件处理程序的方法您可能会看到:事件处理程序属性内联事件处理程序

事件处理程序属性

可以触发事件的对象(例如按钮)通常也具有属性,其名称为 on 后跟事件的名称。例如,元素具有属性 onclick。这称为事件处理程序属性。要侦听事件,您可以将处理程序函数分配给该属性。

例如,我们可以像这样重写随机颜色示例

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

btn.onclick = () => {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
};

您还可以将处理程序属性设置为命名函数

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

function bgChange() {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;

使用事件处理程序属性,您不能为单个事件添加多个处理程序。例如,您可以多次在元素上调用 addEventListener('click', handler),并在第二个参数中指定不同的函数

js
element.addEventListener("click", function1);
element.addEventListener("click", function2);

使用事件处理程序属性这是不可能的,因为任何后续尝试设置属性都会覆盖之前的属性

js
element.onclick = function1;
element.onclick = function2;

内联事件处理程序——不要使用这些

您也可能在代码中看到类似这样的模式

html
<button onclick="bgChange()">Press me</button>
js
function bgChange() {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  document.body.style.backgroundColor = rndCol;
}

在 Web 上找到的最早的注册事件处理程序的方法涉及 事件处理程序 HTML 属性(或内联事件处理程序),如上面所示——属性值实际上是您希望在事件发生时运行的 JavaScript 代码。上面的示例在同一页面上的 <script> 元素内定义的函数,但您也可以在属性内直接插入 JavaScript,例如

html
<button onclick="alert('Hello, this is my old-fashioned event handler!');">
  Press me
</button>

您可以为许多事件处理程序属性找到 HTML 属性等价物;但是,您不应该使用这些——它们被认为是不好的做法。如果您正在做一些非常快速的事情,使用事件处理程序属性似乎很容易,但它们很快就会变得难以管理且效率低下。

首先,将 HTML 和 JavaScript 混合在一起不是一个好主意,因为它会变得难以阅读。将 JavaScript 保持独立是一个好习惯,如果它在单独的文件中,您可以将其应用于多个 HTML 文档。

即使在一个文件中,内联事件处理程序也不是一个好主意。一个按钮没问题,但如果有 100 个按钮呢?您必须向文件中添加 100 个属性;它很快就会变成一场维护噩梦。使用 JavaScript,您可以轻松地将事件处理程序函数添加到页面上的所有按钮,无论有多少个,可以使用类似以下内容

js
const buttons = document.querySelectorAll("button");

for (const button of buttons) {
  button.addEventListener("click", bgChange);
}

最后,许多常见的服务器配置会禁止内联 JavaScript,作为一项安全措施。

您永远不应该使用 HTML 事件处理程序属性——这些已经过时,使用它们是不好的做法。

事件对象

有时,在事件处理程序函数内部,您会看到一个参数,其名称为 eventevte。这称为 **事件对象**,它会自动传递给事件处理程序以提供额外的功能和信息。例如,让我们稍微重新编写一下我们的随机颜色示例

js
const btn = document.querySelector("button");

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

function bgChange(e) {
  const rndCol = `rgb(${random(255)} ${random(255)} ${random(255)})`;
  e.target.style.backgroundColor = rndCol;
  console.log(e);
}

btn.addEventListener("click", bgChange);

注意:您可以在 GitHub 上找到此示例的 完整源代码(还可以 查看其在线运行情况)。

在这里,您可以看到我们在函数中包含了一个事件对象 e,并且在函数中在 e.target 上设置了背景颜色样式——即按钮本身。事件对象的 target 属性始终是对事件发生时元素的引用。因此,在此示例中,我们正在按钮上设置随机背景颜色,而不是在页面上。

注意:您可以为事件对象使用任何您喜欢的名称——您只需要选择一个名称,然后可以在事件处理程序函数中使用它来引用它。e/evt/event 是开发人员最常用的,因为它们简短易记。保持一致性始终是一个好习惯——对自己,以及在可能的情况下对其他人。

事件对象的额外属性

大多数事件对象都有一组标准的属性和方法可用于事件对象;请参阅Event对象参考以获取完整列表。

一些事件对象添加了与其特定事件类型相关的额外属性。例如,当用户按下某个键时,会触发keydown事件。其事件对象是KeyboardEvent,它是一个专门的Event对象,具有一个key属性,该属性告诉您按下了哪个键。

html
<input id="textBox" type="text" />
<div id="output"></div>
js
const textBox = document.querySelector("#textBox");
const output = document.querySelector("#output");
textBox.addEventListener("keydown", (event) => {
  output.textContent = `You pressed "${event.key}".`;
});

尝试在文本框中输入内容并查看输出。

阻止默认行为

有时,您会遇到想要阻止事件执行其默认操作的情况。最常见的示例是 Web 表单,例如自定义注册表单。当您填写详细信息并单击提交按钮时,其自然行为是将数据提交到服务器上的指定页面进行处理,并且浏览器将重定向到某种“成功消息”页面(或者如果未指定其他页面,则重定向到同一页面)。

问题在于用户没有正确提交数据——作为开发人员,您希望阻止提交到服务器,并显示一条错误消息,说明哪里出错以及需要做些什么才能纠正问题。一些浏览器支持自动表单数据验证功能,但由于许多浏览器不支持,因此建议您不要依赖这些功能并实现自己的验证检查。让我们来看一个简单的例子。

首先,一个简单的 HTML 表单,要求您输入您的名字和姓氏。

html
<form>
  <div>
    <label for="fname">First name: </label>
    <input id="fname" type="text" />
  </div>
  <div>
    <label for="lname">Last name: </label>
    <input id="lname" type="text" />
  </div>
  <div>
    <input id="submit" type="submit" />
  </div>
</form>
<p></p>

现在是一些 JavaScript 代码——在这里,我们为submit事件(当表单提交时,会在表单上触发提交事件)实现了一个非常简单的检查,该检查测试文本字段是否为空。如果为空,我们在事件对象上调用preventDefault()函数——这将停止表单提交——然后在表单下方的段落中显示一条错误消息,告诉用户哪里出错了。

js
const form = document.querySelector("form");
const fname = document.getElementById("fname");
const lname = document.getElementById("lname");
const para = document.querySelector("p");

form.addEventListener("submit", (e) => {
  if (fname.value === "" || lname.value === "") {
    e.preventDefault();
    para.textContent = "You need to fill in both names!";
  }
});

显然,这是一个非常弱的表单验证——例如,它不会阻止用户使用空格或数字填充字段来验证表单——但它对于示例目的来说是可以的。输出如下所示。

注意:有关完整源代码,请参阅preventdefault-validation.html(也可以在这里在线查看)。

不仅仅是网页

事件并非 JavaScript 独有——大多数编程语言都具有一定的事件模型,并且模型的工作方式通常与 JavaScript 的方式不同。实际上,网页中 JavaScript 的事件模型与 JavaScript 在其他环境中使用的事件模型不同。

例如,Node.js 是一种非常流行的 JavaScript 运行时,它使开发人员能够使用 JavaScript 构建网络和服务器端应用程序。Node.js 事件模型 依赖于侦听器来侦听事件和发射器来定期发出事件——听起来差别不大,但代码却大不相同,使用了诸如on()之类的函数来注册事件侦听器,以及once()来注册在运行一次后注销的事件侦听器。HTTP connect 事件文档提供了一个很好的示例。

您还可以使用 JavaScript 构建跨浏览器插件——浏览器功能增强——使用称为WebExtensions的技术。事件模型类似于 Web 事件模型,但略有不同——事件侦听器的属性采用驼峰式大小写(例如onMessage而不是onmessage),并且需要与addListener函数结合使用。请参阅runtime.onMessage页面以获取示例。

在这个学习阶段,您无需了解其他此类环境的任何信息;我们只是想清楚地表明,事件在不同的编程环境中可能有所不同。

结论

在本节中,我们学习了什么是事件、如何侦听事件以及如何响应事件。

您现在已经看到,网页中的元素可以嵌套在其他元素中。例如,在阻止默认行为示例中,我们有一些文本框,放置在<div>元素中,这些元素又放置在<form>元素中。当将单击事件侦听器附加到<form>元素,并且用户在其中一个文本框内单击时会发生什么?这称为事件冒泡,是下一章的主题。