事件介绍

事件是你在编程的系统中发生的事情,系统会告诉你这些事情,以便你的代码可以对它们做出反应。例如,如果用户点击网页上的按钮,你可能希望通过显示信息框来对此操作做出反应。在本文中,我们将讨论围绕事件的一些重要概念,并介绍它们在浏览器中工作的基础知识。

预备知识 了解 HTMLCSS 基础,熟悉前面课程中介绍的 JavaScript 基础。
学习成果
  • 什么是事件 — 当发生重要事情时,浏览器发出的信号,开发人员可以运行一些代码来响应。
  • 使用 addEventListener()(和 removeEventListener())和事件处理程序属性设置事件处理程序。
  • 内联事件处理程序属性,以及为什么不应该使用它们。
  • 事件对象。

什么是事件?

事件是你在编程的系统中发生的事情——当事件发生时,系统会产生(或“触发”)某种信号,并提供一种机制,通过该机制,当事件发生时可以自动采取行动(即运行一些代码)。事件在浏览器窗口内触发,并且往往附加到其中存在的特定项目。这可能是一个元素、一组元素、当前标签页中加载的 HTML 文档,或者整个浏览器窗口。可以发生许多不同类型的事件。

例如

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

你可以从这里(并从浏览事件索引)了解到,可以触发的事件非常多

要对事件做出反应,你需要为其附加一个事件监听器。这是一个监听事件触发的代码功能。当事件触发时,会调用一个事件处理程序函数(由事件监听器引用或包含在事件监听器中)来响应事件的触发。当这样一段代码被设置为响应事件而运行时,我们称之为注册事件处理程序

一个例子:处理点击事件

在以下示例中,页面上有一个<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;
});

当用户点击 <button> HTML 元素时,它将触发一个 click 事件。我们调用其 addEventListener() 方法来添加事件监听器;它接受两个参数

  • 字符串 "click",表示我们要监听 click 事件。按钮可以触发许多其他事件,例如当用户将鼠标移到按钮上方时的 "mouseover",或者当用户按下键并且按钮被聚焦时的 "keydown"
  • 事件发生时调用的函数。在我们的示例中,定义的匿名函数生成一个随机 RGB 颜色,并将页面 <body>background-color 设置为该颜色。

你也可以创建一个单独的命名函数,并在 addEventListener() 的第二个参数中引用它,如下所示

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() 方法。例如,以下行将删除我们之前看到的 click 事件处理程序

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

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

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

通过多次调用 addEventListener(),提供不同的处理程序,你可以让多个处理程序函数响应单个事件运行

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

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

其他事件监听机制

我们建议你使用 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() 相比,事件处理程序属性具有缺点。其中最重要的是你不能为单个事件添加多个监听器。以下模式不起作用,因为任何后续设置属性值的尝试都会覆盖前面的值

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 属性总是对事件发生元素的引用。所以,在这个例子中,我们为按钮设置了一个随机背景颜色,而不是页面。

注意:你可以为事件对象使用任何你喜欢的名称——你只需要选择一个可以在事件处理程序函数中引用的名称。eevtevent 是开发人员常用的名称,因为它们简短易记。保持一致性总是好的——与你自己,如果可能的话,也与其他人保持一致。

事件对象的额外属性

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

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

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 action="#">
  <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 事件(表单提交时触发 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() 来注册一个在运行一次后就注销的事件监听器。Node.js HTTP connect 事件文档提供了一个很好的例子。

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

在学习的这个阶段,你不需要了解其他此类环境的任何信息;我们只是想明确一点,事件在不同的编程环境中可能有所不同。

总结

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

你已经看到,网页中的元素可以嵌套在其他元素中。例如,在阻止默认行为示例中,我们有一些文本框,它们放置在 <div> 元素中,而这些元素又放置在 <form> 元素中。当一个点击事件监听器附加到 <form> 元素上,并且用户点击其中一个文本框内部时会发生什么?相关的事件处理程序函数仍然通过一个称为事件冒泡的过程触发,这将在下一课中介绍。