翻译正在进行中。

事件是你在编程时系统内的发生的动作或者发生的事情,系统通过它来告诉你在你愿意的情况下你可以以某种方式对它做出回应。例如:如果你在网页上单击一个按钮,你可能想通过显示一个信息框来响应这个动作。在这篇文章中我们将围绕事件讨论一些重要的概念,并且观察它们在浏览器上是怎么工作的。这篇文章并不做彻底的研究仅聚焦于你现阶段需要掌握的知识。

前提: 基本电脑知识, 对HTML和Css的基本了解,及 JavaScript first steps.
目标: 了解事件的基本理论,它们怎么在浏览器上运行的, 以及在不同的编程环境下事件怎么不同.

A series of fortunate events

就像上面提到的, 事件是你在编程时系统内的发生的动作或者发生的事情— 系统会在事件出现的时候触发某种信号并且会提供一个自动加载某种动作(列如:运行一些代码)的机制,比如在一个机场,当一架将起飞的飞机的跑道清理完成后,飞行员会收到一个信号,结果是他们开始起飞。

在Web中, 事件在浏览器窗口中被触发并且通常被绑定到窗口内部的特定部分 — 可能是一个元素、一系列元素、被加载到这个窗口的 HTML 代码或者是整个浏览器窗口。举几个可能发生的不同事件:

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

如果你想看看更多其他的事件 (移步到MDN的 Event reference) 。

每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器。注意事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相通的,尽管严格地说来这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。

Note: 网络事件不是 JavaScript 语言的核心——它们被定义成 JavaScript APIs 内置在浏览器。

A simple example

让我们看一个简单的例子。前面你已经见到过很多事件和事件监听器,现在我们概括一下以巩固我们的知识。在接下来的例子中,我们的页面中只有一个 button,按下时,背景会变成随即的一种颜色。

<button>Change color</button>

The JavaScript looks like so:

var btn = document.querySelector('button');

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

btn.onclick = function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

我们使用 btn 变量存储 button,并使用了 Document.querySelector() 函数。我们也定义了一个返回随机数字的函数。代码第三部分就是事件处理器。btn 变量指向 button 元素,在 button 这种对象上可触发一系列的事件,因此也就可以使用事件处理器。我们通过将一个匿名函数(这个赋值函数包括生成随机色并赋值给背景色的代码)赋值给“点击”事件处理器参数,监听“点击”这个事件。

This code will now be run whenever the click event fires on the <button> element, i.e., whenever a user clicks on it.

The example output is as follows:

It's not just web pages

值得注意的是并不是只有 JavaScript 使用事件——大多的编程语言都有这种机制,并且它们的工作方式不同于 JavaScript。实际上,JavaScript 网页上的事件机制不同于在其他环境中的事件机制。

比如, Node.js 是一种非常流行的允许开发者使用 JavaScript 来建造网络和服务器端应用的运行环境。Node.js event model 依赖定期监听事件的监听器和定期处理事件的处理器——虽然听起来好像差不多,但是实现两者的代码是非常不同的,Node.js 使用像 on ( ) 这样的函数来注册一个事件监听器,使用 once ( ) 这样函数来注册一个在运行一次之后注销的监听器。 HTTP connect event docs 提供了很多例子。

另外一个例子:你可以使用 JavaScript 来开发跨浏览器的插件(使用 WebExtensions 开发技术。事件模型和网站的事件模型是相似的,仅有一点点不同——事件监听属性是 camel-cased (e.g. onMessage rather than onmessage), and need to be combined with the addListener function. See the runtime.onMessage page for an example.

你现在不需要掌握这些,我们只想申明不同的编程环境下事件机制是不同的,

Ways of using web events

额 添加事件监听器有非常多的方法。

Event handler properties

These are the properties that exist to contain event handler code that we have seen most frequently during the course. Returning to the above example:

var btn = document.querySelector('button');

btn.onclick = function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

这个 onclick 是被用在这个情景下的事件处理器的属性,它就像 button 其他的属性(如 btn.textContent, or btn.style), 但是有一个特别的地方——当你将一些代码赋值给它的时候,只要事件触发代码就会运行。

你也可以将一个有名字的函数赋值给事件处理参数(正如我们在 Build your own function 中看到的),下面的代码也是这样工作的:

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;

有很多事件处理参数可供选择,我们来做一个实验。

首先将 random-color-eventhandlerproperty.html 复制到本地,并且字啊浏览器打开。将btn.onclick 依次换成其他值,在浏览器中观察效果。a

  • btn.onfocus and btn.onblur — The color will change when the button is focused and unfocused (try pressing tab to tab on to the button and off again). These are often used to display information about how to fill in form fields when they are focused, or display an error message if a form field has just been filled in with an incorrect value.
  • btn.ondblclick — The color will change only when it is double-clicked.
  • window.onkeypress, window.onkeydown, window.onkeyup — The color will change when a key is pressed on the keyboard. keypress refers to a general press (button down and then up), while keydown and keyup refer to just the key down and key up parts of the keystroke, respectively. Note that it doesn't work if you try to register this event handler on the button itself — we've had to register it on the window object, which represents the entire browser window.
  • btn.onmouseover and btn.onmouseout — The color will change when the mouse pointer is moved so it begins hovering over the button, or when it stops hover over the button and moves off it, respectively.

一些事件非常通用,几乎在任何地方都可以用(比如 onclick 几乎可以用在几乎每一个元素上),然而另一些元素就只能在特定场景下使用,比如我们只能在 video 元素上使用 onplay 。

Inline event handlers — don't use these

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

<button onclick="bgChange()">Press me</button>
function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

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

The earliest method of registering event handlers found on the Web involved event handler HTML attributes (aka 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:

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

You'll 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 just doing something really quick, but they very quickly become unmanageable and inefficient. 

一开始,你不应该混用 HTML 和 JavaScript,因为这样文档很难解析——最好的办法是只在一块地方写 JavaScript 代码。

即使在单一文件中,内置事件处理器也不是一个好主意。一个按钮看起来还好,但是如果有一百个按钮呢?你得在文件中加上100个属性。这很快就会成为维护人员的噩梦。使用 Java Script,你可以给网页中的 button 都加上事件处理器。就像下面这样:

var buttons = document.querySelectorAll('button');

for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}

Note: Separating your programming logic from your content also makes your site more friendly to search engines.

addEventListener() and removeEventListener()

新的事件触发机制被定义在 Document Object Model (DOM) Level 2 Events Specification, 这个细则给浏览器提供了一个函数 — addEventListener()。这个函数和事件处理属性是类似的,但是语法略有不同。我们可以重写上面的随即颜色背景代码:

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}   

btn.addEventListener('click', bgChange);

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

在addEventListener() 函数中, 我们具体化了两个参数——我们想要将处理器应用上去的事件名称,和包含我们用来回应事件的函数的代码。注意将这些代码全部放到一个匿名函数中是可行的:

btn.addEventListener('click', function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
});

This mechanism has some advantages over the older mechanisms discussed earlier. For a start, there is a counterpart function, removeEventListener(), which removes a previously added listener. For example, this would remove the listener set in the first code block in this section:

btn.removeEventListener('click', bgChange);

这在简单个的、小型的项目中可能不是很有用,但是在大型的、复杂的项目中就非常有用了,可以非常高效地清除不用的事件处理器,另外在其他的一些场景中也非常有效——比如你需要在不同环境下运行不同的事件处理器,你只需要恰当地删除或者添加事件处理器即可。

你也可以给同一个监听器注册多个处理器,下面这种方式不能实现这一点:

和imyElement.onclick = functionA;
myElement.onclick = functionB;

第二行会覆盖第一行,但是下面这种方式就会正常工作了:

myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);

当元素被点击时两个函数都会工作:

In addition, there are a number of other powerful features and options available with this event mechanism. These are a little out of scope for this article, but if you want to read up on them, have a look at the addEventListener() and removeEventListener() reference pages.

What mechanism should I use?

在三种机制中,您绝对不应该使用HTML事件处理程序属性 - 这些属性已经过时了,而且也是不好的做法,如上所述.

另外两种是相对可互换的,至少对于简单的用途s:

  • Event handler properties have less power and options, but better cross browser compatibility (being supported as far back as Internet Explorer 8). You should probably start with these as you are learning.
  • DOM Level 2 Events (addEventListener(), etc.) 更强大,但也可以变得更加复杂,并且支持不足(只支持到Internet Explorer 9)。 但是您也应该尝试这个方法,并尽可能地使用它们。

The main advantages of the third mechanism are that you can remove event handler code if needed, using removeEventListener(), and you can add multiple listeners of the same type to elements if required. For example, you can call addEventListener('click', function() { ... }) on an element multiple times, with different functions specified in the second argument. This is impossible with event handler properties, because any subsequent attempts to set a property will overwrite earlier ones, e.g.:

element.onclick = function1;
element.onclick = function2;
etc.

Note: If you are called upon to support browsers older than Internet Explorer 8 in your work, you may run into difficulties, as such ancient browsers use different event models from newer browsers. But never fear, most JavaScript libraries (for example jQuery) have built in functions that abstract away cross browser differences. Don't worry about this too much at this stage in your learning journey.

Other event concepts

In this section we will briefly cover some advanced concepts that are relevant to events. It is not important to understand these fully at this point, but it might serve to explain some code patterns you'll likely come across from time to time.

Event objects

有时候在事件处理函数内部,您可能会看到一个固定指定名称的参数,例如eventevt或简单的e。 这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。 例如,让我们稍稍重写一遍我们的随机颜色示例:

function bgChange(e) {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  e.target.style.backgroundColor = rndCol;
  console.log(e);
}  

btn.addEventListener('click', bgChange);

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

在这里,您可以看到我们在函数中包括一个事件对象,e,并在函数中设置背景颜色样式在e.target上 - 它指的是按钮本身。 事件对象 etarget属性始终是事件刚刚发生的元素的引用。 所以在这个例子中,我们在按钮上设置一个随机的背景颜色,而不是页面。

Note: 您可以使用任何您喜欢的名称作为事件对象 - 您只需要选择一个名称,然后可以在事件处理函数中引用它。 开发人员最常使用 e / evt / event,因为它们很简单易记。 坚持标准总是很好。

当您要在多个元素上设置相同的事件处理程序时,e.target非常有用,并且在发生事件时对所有元素执行某些操作. You might for example have a set of 16 tiles that disappear when they are clicked on. It is useful to always be able to just set the thing to disappear as e.target, rather than having to select it in some more difficult way. In the following example (see useful-eventtarget.html for the full source code; also see it running live here), we create 16 <div> elements using JavaScript. We then select all of them using document.querySelectorAll(), then loop through each one, adding an onclick handler to each that makes it so that a random color is applied to each one when clicked:

var divs = document.querySelectorAll('div');

for (var i = 0; i < divs.length; i++) {
  divs[i].onclick = function(e) {
    e.target.style.backgroundColor = bgChange();
  }
}

The output is as follows (try clicking around on it — have fun):

Most event handlers you'll encounter just have a standard set of properties and functions (methods) available on the event object (see the Event object reference for a full list). Some more advanced handlers however add specialist properties containing extra data that they need to function. The Media Recorder API for example has a dataavailable event, which fires when some audio or video has been recorded and is available for doing something with (for example saving it, or playing it back). The corresponding ondataavailable handler's event object has a data property available containing the recorded audio or video data to allow you to access it and do something with it.

Preventing default behaviour

有时,您会遇到一写情况,您希望事件不执行它的默认行为。 最常见的例子是Web表单,例如自定义注册表单。 当您填写详细信息并按提交按钮时,自然行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(或 相同的页面,如果另一个没有指定。)

当用户没有正确提交数据时,麻烦就来了 - 作为开发人员,您希望停止提交信息给服务器,并给他们一个错误提示,告诉他们什么做错了,以及需要做些什么来修正错误。 一些浏览器支持自动的表单数据验证功能,但由于许多浏览器不支持,因此建议您不要依赖这些功能,并实现自己的验证检查。 我们来看一个简单的例子。

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

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

 

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

var form = document.querySelector('form');
var fname = document.getElementById('fname');
var lname = document.getElementById('lname');
var submit = document.getElementById('submit');
var para = document.querySelector('p');

form.onsubmit = function(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 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:

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

Event bubbling and capture

The final subject to cover here is something that you'll not come across often, but it can be a real pain if you don't understand it. Event bubbling and capture 是描述在同一个元素上同时激活两个相同事件类型的不同的事件处理程序时会发生什么的两种机制. Let's look at an example to make this easier — open up the show-video-box.html example in a new tab (and the source code in another tab.) It is also available live below:

This is a pretty simple example that shows and hides a <div> with a <video> element inside it:

<button>Display video</button>

<div class="hidden">
  <video>
    <source src="rabbit320.mp4" type="video/mp4">
    <source src="rabbit320.webm" type="video/webm">
    <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
  </video>
</div>

When the <button> is clicked, the video is displayed, by changing the class attribute on the <div> from hidden to showing (the example's CSS contains these two classes, which position the box off the screen and on the screen, respectively):

btn.onclick = function() {
  videoBox.setAttribute('class', 'showing');
}

We then add a couple more onclick event handlers — the first one to the <div> and the second one to the <video>. The idea is that when the area of the <div> outside the video is clicked, the box should be hidden again; when the video itself is clicked, the video should start to play.

videoBox.onclick = function() {
  videoBox.setAttribute('class', 'hidden');
};

video.onclick = function() {
  video.play();
};

但是有一个问题 - 当你点击video开始播放的视频时,它会在同一时间导致<div>也被隐藏。 这是因为video<div>之内 - video<div>的一个子元素 - 所以点击video实际上是同时也运行<div>上的事件处理程序。

Bubbling and capturing explained

当一个事件触发了一个有父元素的元素(例如我们的<video>时),现代浏览器运行两个不同的阶段 - 捕获阶段和冒泡阶段。 在捕获阶段:

  • 浏览器检查元素的最外层祖先(<html>)是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
  • 然后,它移动到<html>中的下一个元素,并执行相同的操作,然后是下一个元素,依此类推,直到到达实际点击的元素。

在冒泡阶段,恰恰相反:

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它
  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。

(Click on image for bigger diagram)

在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。 So in our current example, when you click the video, the click event bubbles from the <video> element outwards to the <html> element. Along the way:

  • It finds the video.onclick... handler and runs it, so the video first starts playing.
  • It then finds the videoBox.onclick... handler and runs it, so the video is hidden as well.

Fixing the problem with stopPropagation()

这是令人讨厌的行为,但有一种方法来解决它!标准事件对象具有可用的名为 stopPropagation()的函数, 当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在bubble链上进一步扩大,因此不会再有任何以外的事件处理程序运行。We can therefore fix our current problem by changing the second handler function in the previous code block to this:

video.onclick = function(e) {
  e.stopPropagation();
  video.play();
};

You can try making a local copy of the show-video-box.html source code and having a go at fixing it yourself, or looking at the fixed result in show-video-box-fixed.html (also see the source code here).

Note: Why bother with both capturing and bubbling? Well, in the bad old days when browsers were much less cross-compatible than they are now, Netscape used only event capturing, and Internet Explorer used only event bubbling. When the W3C decided to try to standardize the behaviour and reach a consensus, they ended up with this system that included both, which is the one modern browsers implemented.

Note: As mentioned above, by default all event handlers are registered in the bubbling phase, and this makes more sense most of the time. If you really want to register an event in the capturing phase instead, you can do so by registering your handler using addEventListener(), and setting the optional third property to true.

Event delegation

Bubbling also allows us to take advantage of event delegation — this concept relies on the fact that if you want some code to run when you click on any one of a large number of child elements, you can set the event listener on their parent and have the effect of the event listener bubble to each child, rather than having to set the event listener on every child individually.

A good example is a series of list items — if you want each one of them to pop up a message when clicked, you can can set the click event listener on the parent <ul>, and it will bubble to the list items.

This concept is explained further on David Walsh's blog, with multiple examples — see How JavaScript Event Delegation Works.

Conclusion

You should now know all you need to know about web events at this early stage. As mentioned above, events are not really part of the core JavaScript — they are defined in browser Web APIs.

Also, it is important to understand that the different contexts that JavaScript is used in tend to have different event models — from Web APIs to other areas such as browser WebExtensions and Node.js (server-side JavaScript). We are not expecting you to understand all these areas now, but it certainly helps to understand the basics of events as you forge ahead with learning web development.

If there is anything you didn't understand, feel free to read through the article again, or contact us to ask for help.

See also

  • Event order (discussion of capturing and bubbling) — an excellently detailed piece by Peter-Paul Koch.
  • Event accessing (discussing of the event object) — another excellently detailed piece by Peter-Paul Koch.
  • Event reference

文档标签和贡献者

 此页面的贡献者: ziyunfei, carolinezhao, Ende93, finegao, Zeng, Hayden_Zhou
 最后编辑者: carolinezhao,