事件简介

事件是你正在编程的系统中发生的事情,系统会告诉你这些信息,以便你的代码可以对它们做出反应。

¥Events are things that happen in the system you are programming, which the system tells you about so your code can react to them.

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

¥For example, if the user clicks a button on a webpage, you might want to react to that action by displaying an information box. In this article, we discuss some important concepts surrounding events, and look at how they work in browsers. This won't be an exhaustive study; just what you need to know at this stage.

先决条件: 对 HTML、CSS 和 JavaScript 第一步 有基本了解。
目标: 了解事件的基本理论、事件在浏览器中的工作方式以及事件在不同的编程环境中有何不同。

什么是事件?

¥What is an event?

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

¥Events are things that happen in the system you are programming — the system produces (or "fires") a signal of some kind when an event occurs, and provides a mechanism by which an action can be automatically taken (that is, some code running) when the event occurs. Events are fired inside the browser window, and tend to be attached to a specific item that resides in it. This might be a single element, a set of elements, the HTML document loaded in the current tab, or the entire browser window. There are many different types of events that can occur.

例如:

¥For example:

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

你可以从此(以及浏览 MDN 事件参考)了解到有很多可以触发的事件。

¥You can gather from this (and from glancing at the MDN event reference) that there are a lot of events that can be fired.

要对事件做出反应,你可以将事件处理程序附加到该事件。这是在事件触发时运行的一段代码(通常是你作为程序员创建的 JavaScript 函数)。当这样的代码块被定义为响应事件而运行时,我们说我们正在注册一个事件处理程序。注意:事件处理程序有时称为事件监听器 - 对于我们的目的来说,它们几乎可以互换,尽管严格来说,它们是一起工作的。监听器监听事件发生,处理程序是响应事件发生而运行的代码。

¥To react to an event, you attach an event handler to it. This is a block of code (usually a JavaScript function that you as a programmer create) that runs when the event fires. When such a block of code is defined to run in response to an event, we say we are registering an event handler. Note: Event handlers are sometimes called event listeners — they are pretty much interchangeable for our purposes, although strictly speaking, they work together. The listener listens out for the event happening, and the handler is the code that is run in response to it happening.

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

¥Note: Web events are not part of the core JavaScript language — they are defined as part of the APIs built into the browser.

一个例子:处理点击事件

¥An example: handling a click event

在以下示例中,页面中有一个 <button>

¥In the following example, we have a single <button> in the page:

html
<button>Change color</button>
css
button {
  margin: 10px;
}

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

¥Then we have some JavaScript. We'll look at this in more detail in the next section, but for now we can just say: it adds an event handler to the button's "click" event, and the handler reacts to the event by setting the page background to a random color:

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;
});

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

¥The example output is as follows. Try clicking the button:

使用 addEventListener()

¥Using addEventListener()

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

¥As we saw in the last example, objects that can fire events have an addEventListener() method, and this is the recommended mechanism for adding event handlers.

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

¥Let's take a closer look at the code from the last example:

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() 函数,我们在这里调用它。我们传入两个参数:

¥The HTML <button> element will fire an event when the user clicks the button. So it defines an addEventListener() function, which we are calling here. We're passing in two parameters:

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

可以将处理函数设置为单独的命名函数,如下所示:

¥It is fine to make the handler function a separate named function, like this:

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);

监听其他事件

¥Listening for other events

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

¥There are many different events that can be fired by a button element. Let's experiment.

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

¥First, make a local copy of random-color-addeventlistener.html, and open it in your browser. It's just a copy of the simple random color example we've played with already. Now try changing click to the following different values in turn, and observing the results in the example:

  • focusblur — 按钮聚焦和未聚焦时颜色发生变化;尝试按选项卡将焦点集中在按钮上,然后再次按选项卡将焦点从按钮上移开。这些通常用于在获得焦点时显示有关填写表单字段的信息,或者在表单字段填充了不正确的值时显示错误消息。
  • dblclick — 仅当双击按钮时颜色才会改变。
  • mouseovermouseout — 当鼠标指针悬停在按钮上或指针移离按钮时,颜色分别发生变化。

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

¥Some events, such as click, are available on nearly any element. Others are more specific and only useful in certain situations: for example, the play event is only available on some elements, such as <video>.

删除监听器

¥Removing listeners

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

¥If you've added an event handler using addEventListener(), you can remove it again using the removeEventListener() method. For example, this would remove the changeBackground() event handler:

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

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

¥Event handlers can also be removed by passing an AbortSignal to addEventListener() and then later calling abort() on the controller owning the AbortSignal. For example, to add an event handler that we can remove with an 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
);

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

¥Then the event handler created by the code above can be removed like this:

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

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

¥For simple, small programs, cleaning up old, unused event handlers isn't necessary, but for larger, more complex programs, it can improve efficiency. Also, the ability to remove event handlers allows you to have the same button performing different actions in different circumstances: all you have to do is add or remove handlers.

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

¥Adding multiple listeners for a single event

通过对 addEventListener() 进行多次调用并提供不同的处理程序,你可以为单个事件拥有多个处理程序:

¥By making more than one call to addEventListener(), providing different handlers, you can have multiple handlers for a single event:

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

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

¥Both functions would now run when the element is clicked.

了解更多

¥Learn more

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

¥There are other powerful features and options available with addEventListener().

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

¥These are a little out of scope for this article, but if you want to read them, visit the addEventListener() and removeEventListener() reference pages.

其他事件监听机制

¥Other event listener mechanisms

我们建议你使用 addEventListener() 来注册事件处理程序。它是最强大的方法,并且可以最好地适应更复杂的程序。但是,你可能会看到其他两种注册事件处理程序的方法:事件处理程序属性和内联事件处理程序。

¥We recommend that you use addEventListener() to register event handlers. It's the most powerful method and scales best with more complex programs. However, there are two other ways of registering event handlers that you might see: event handler properties and inline event handlers.

事件处理程序属性

¥Event handler properties

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

¥Objects (such as buttons) that can fire events also usually have properties whose name is on followed by the name of the event. For example, elements have a property onclick. This is called an event handler property. To listen for the event, you can assign the handler function to the property.

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

¥For example, we could rewrite the random-color example like this:

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;
};

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

¥You can also set the handler property to a named function:

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),并在第二个参数中指定不同的函数:

¥With event handler properties, you can't add more than one handler for a single event. For example, you can call addEventListener('click', handler) on an element multiple times, with different functions specified in the second argument:

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

对于事件处理程序属性来说这是不可能的,因为任何后续尝试设置该属性都会覆盖之前的尝试:

¥This is impossible with event handler properties because any subsequent attempts to set the property will overwrite earlier ones:

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

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

¥Inline event handlers — don't use these

你可能还会在代码中看到这样的模式:

¥You might also see a pattern like this in your code:

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,例如:

¥The earliest method of registering event handlers found on the Web involved event handler HTML attributes (or inline event handlers) like the one shown above — the attribute value is literally the JavaScript code you want to run when the event occurs. The above example invokes a function defined inside a <script> element on the same page, but you could also insert JavaScript directly inside the attribute, for example:

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

你可以找到许多事件处理程序属性的 HTML 属性等效项;然而,你不应该使用它们 - 它们被认为是不好的做法。如果你做的事情非常快,那么使用事件处理程序属性似乎很容易,但它们很快就会变得难以管理且效率低下。

¥You can find HTML attribute equivalents for many of the event handler properties; however, you shouldn't use these — they are considered bad practice. It might seem easy to use an event handler attribute if you are doing something really quick, but they quickly become unmanageable and inefficient.

首先,混合 HTML 和 JavaScript 并不是一个好主意,因为它会变得难以阅读。将 JavaScript 保持独立是一个很好的做法,如果它位于单独的文件中,你可以将其应用到多个 HTML 文档。

¥For a start, it is not a good idea to mix up your HTML and your JavaScript, as it becomes hard to read. Keeping your JavaScript separate is a good practice, and if it is in a separate file you can apply it to multiple HTML documents.

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

¥Even in a single file, inline event handlers are not a good idea. One button is OK, but what if you had 100 buttons? You'd have to add 100 attributes to the file; it would quickly turn into a maintenance nightmare. With JavaScript, you could easily add an event handler function to all the buttons on the page no matter how many there were, using something like this:

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

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

最后,作为一种安全措施,许多常见的服务器配置将不允许内联 JavaScript。

¥Finally, many common server configurations will disallow inline JavaScript, as a security measure.

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

¥You should never use the HTML event handler attributes — those are outdated, and using them is bad practice.

事件对象

¥Event objects

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

¥Sometimes, inside an event handler function, you'll see a parameter specified with a name such as event, evt, or e. This is called the event object, and it is automatically passed to event handlers to provide extra features and information. For example, let's rewrite our random color example again slightly:

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 上找到此示例的 完整源代码(也可以找到 看到它实时运行)。

¥Note: You can find the full source code for this example on GitHub (also see it running live).

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

¥Here you can see we are including an event object, e, in the function, and in the function setting a background color style on e.target — which is the button itself. The target property of the event object is always a reference to the element the event occurred upon. So, in this example, we are setting a random background color on the button, not the page.

注意:你可以为事件对象使用任何你喜欢的名称 - 你只需选择一个名称,然后可以使用该名称在事件处理程序函数中引用它。e/evt/event 是开发者最常用的,因为它们简短且易于记忆。保持一致总是好的 - 与自己保持一致,如果可能的话与他人保持一致。

¥Note: You can use any name you like for the event object — you just need to choose a name that you can then use to reference it inside the event handler function. e/evt/event is most commonly used by developers because they are short and easy to remember. It's always good to be consistent — with yourself, and with others if possible.

事件对象的额外属性

¥Extra properties of event objects

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

¥Most event objects have a standard set of properties and methods available on the event object; see the Event object reference for a full list.

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

¥Some event objects add extra properties that are relevant to that particular type of event. For example, the keydown event fires when the user presses a key. Its event object is a KeyboardEvent, which is a specialized Event object with a key property that tells you which key was pressed:

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}".`;
});
css
div {
  margin: 0.5rem 0;
}

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

¥Try typing into the text box and see the output:

防止默认行为

¥Preventing default behavior

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

¥Sometimes, you'll come across a situation where you want to prevent an event from doing what it does by default. The most common example is that of a web form, for example, a custom registration form. When you fill in the details and click the submit button, the natural behavior is for the data to be submitted to a specified page on the server for processing, and the browser to be redirected to a "success message" page of some kind (or the same page, if another is not specified).

当用户没有正确提交数据时,麻烦就会出现 - 作为开发者,你希望阻止向服务器提交数据,并给出一条错误消息,说明出了什么问题以及需要采取哪些措施来纠正错误。某些浏览器支持自动表单数据验证功能,但由于许多浏览器不支持,因此建议你不要依赖这些功能并实现自己的验证检查。让我们看一个简单的例子。

¥The trouble comes when the user has not submitted the data correctly — as a developer, you want to prevent the submission to the server and give an error message saying what's wrong and what needs to be done to put things right. Some browsers support automatic form data validation features, but since many don't, you are advised to not rely on those and implement your own validation checks. Let's look at a simple example.

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

¥First, a simple HTML form that requires you to enter your first and last name:

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>
css
div {
  margin-bottom: 10px;
}

现在是一些 JavaScript - 这里我们在 submit 事件的处理程序中实现了一个非常简单的检查(表单提交时会触发提交事件),以测试文本字段是否为空。如果是,我们在事件对象上调用 preventDefault() 函数 - 这会停止表单提交 - 然后在表单下面的段落中显示一条错误消息,告诉用户出了什么问题:

¥Now some JavaScript — here we implement a very simple check inside a handler for the submit event (the submit event is fired on a form when it is submitted) that tests whether the text fields are empty. If they are, we call the preventDefault() function on the event object — which stops the form submission — and then display an error message in the paragraph below our form to tell the user what's wrong:

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!";
  }
});

显然,这是相当弱的表单验证 - 例如,它不会阻止用户使用在字段中输入的空格或数字来验证表单 - 但出于示例目的,这是可以的。输出如下:

¥Obviously, this is pretty weak form validation — it wouldn't stop the user from validating the form with spaces or numbers entered into the fields, for example — but it is OK for example purposes. The output is as follows:

注意:有关完整的源代码,请参阅 preventdefault-validation.html(另请参阅此处的 实时运行)。

¥Note: For the full source code, see preventdefault-validation.html (also see it running live here).

这不仅仅是网页

¥It's not just web pages

事件并不是 JavaScript 所独有的 - 大多数编程语言都有某种事件模型,并且该模型的工作方式通常与 JavaScript 的方式不同。事实上,网页 JavaScript 中的事件模型与其他环境中使用的 JavaScript 事件模型不同。

¥Events are not unique to JavaScript — most programming languages have some kind of event model, and the way the model works often differs from JavaScript's way. In fact, the event model in JavaScript for web pages differs from the event model for JavaScript as it is used in other environments.

例如,Node.js 是一个非常流行的 JavaScript 运行时,它使开发者能够使用 JavaScript 来构建网络和服务器端应用。Node.js 事件模型 依靠监听器来监听事件,并依靠触发器定期触发事件 - 听起来并没有什么不同,但代码却截然不同,利用 on() 等函数来注册事件监听器,使用 once() 来注册事件监听器 运行一次后取消注册。HTTP 连接事件文档 就是一个很好的例子。

¥For example, Node.js is a very popular JavaScript runtime that enables developers to use JavaScript to build network and server-side applications. The Node.js event model relies on listeners to listen for events and emitters to emit events periodically — it doesn't sound that different, but the code is quite different, making use of functions like on() to register an event listener, and once() to register an event listener that unregisters after it has run once. The HTTP connect event docs provide a good example.

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

¥You can also use JavaScript to build cross-browser add-ons — browser functionality enhancements — using a technology called WebExtensions. The event model is similar to the web events model, but a bit different — event listeners' properties are written in camel case (such as onMessage rather than onmessage), and need to be combined with the addListener function. See the runtime.onMessage page for an example.

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

¥You don't need to understand anything about other such environments at this stage in your learning; we just wanted to make it clear that events can differ in different programming environments.

结论

¥Conclusion

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

¥In this chapter we've learned what events are, how to listen for events, and how to respond to them.

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

¥You've seen by now that elements in a web page can be nested inside other elements. For example, in the Preventing default behavior example, we have some text boxes, placed inside <div> elements, which in turn are placed inside a <form> element. What happens when a click event listener is attached to the <form> element, and the user clicks inside one of the text boxes? This is called event bubbling and is the subject of the next chapter.