事件参考
事件介绍
触发事件是为了通知代码可能影响代码执行的“有趣变化”。这些可能来自用户交互,例如使用鼠标或调整窗口大小,底层环境状态的变化(例如,低电量或来自操作系统的媒体事件)以及其他原因。
每个事件都由一个基于Event接口的对象表示,并且可能有额外的自定义字段和/或函数来提供关于发生的事情的信息。每个事件的文档都有一个表(靠近顶部),其中包括到相关事件接口的链接以及其他相关信息。 Event > Interfaces based on Event中给出了不同事件类型的完整列表。
本主题提供了您可能感兴趣的主要类型事件(动画,剪贴板,workers 等)的索引以及实现这些类型事件的主要类。最后是所有记录事件的扁平列表。
注意:本页列出了许多你在网上会遇到的最常见的事件。如果您正在搜索此处未列出的事件,请尝试在MDN的其余部分搜索其名称、主题区域或相关规范。
Event index
Event listing
本节列出了在MDN上有自己参考页面的事件。如果您对此处未列出的事件感兴趣,请尝试在MDN的其余部分搜索其名称、主题领域或相关规范。
1、创建和触发事件
本文演示了如何创建和分派DOM事件。这些事件通常被称为合成事件(synthetic events
),与浏览器本身触发的事件相反。
1.1 创建自定义事件
事件可以用Event构造函数创建,如下所示:
const event = new Event("build");
// Listen for the event.
elem.addEventListener(
"build",
(e) => {
/* … */
},
false,
);
// Dispatch the event.
elem.dispatchEvent(event);
上面的代码示例使用了EventTarget.dispatchEvent()方法。
大多数现代浏览器都支持此构造函数。要了解更详细的方法,请参见下面的老式方法。
添加自定义数据- CustomEvent()
要向事件对象添加更多数据,存在CustomEvent接口,并且可以使用detail
属性来传递自定义数据。例如,可以这样创建事件:
const event = new CustomEvent("build", { detail: elem.dataset.time });
这将允许您访问事件监听器中的其他数据:
function eventHandler(e) {
console.log(`The time is: ${e.detail}`);
}
老式的方式
创建事件的旧方法使用受Java启发的api。下面是一个使用document.createEvent()的示例:
// Create the event.
const event = document.createEvent("Event");
// Define that the event name is 'build'.
event.initEvent("build", true, true);
// Listen for the event.
elem.addEventListener(
"build",
(e) => {
// e.target matches elem
},
false,
);
// target can be any Element or other EventTarget.
elem.dispatchEvent(event);
事件冒泡
通常希望从子元素触发事件,并让一个祖先捕获它;可选的,带数据:
<form>
<textarea></textarea>
</form>
const form = document.querySelector("form");
const textarea = document.querySelector("textarea");
// Create a new event, allow bubbling, and provide any data you want to pass to the "detail" property
const eventAwesome = new CustomEvent("awesome", {
bubbles: true,
detail: { text: () => textarea.value },
});
// The form element listens for the custom "awesome" event and then consoles the output of the passed text() method
form.addEventListener("awesome", (e) => console.log(e.detail.text()));
// As the user types, the textarea inside the form dispatches/triggers the event to fire, and uses itself as the starting point
textarea.addEventListener("input", (e) => e.target.dispatchEvent(eventAwesome));
动态创建和调度事件
元素可以监听尚未创建的事件:
<form>
<textarea></textarea>
</form>
const form = document.querySelector("form");
const textarea = document.querySelector("textarea");
form.addEventListener("awesome", (e) => console.log(e.detail.text()));
textarea.addEventListener("input", function () {
// Create and dispatch/trigger an event on the fly
// Note: Optionally, we've also leveraged the "function expression" (instead of the "arrow function expression") so "this" will represent the element
this.dispatchEvent(
new CustomEvent("awesome", {
bubbles: true,
detail: { text: () => textarea.value },
}),
);
});
1.2 触发内置事件
这个示例演示了使用DOM方法模拟复选框上的单击(以编程方式生成单击事件)。查看实际示例
function simulateClick() {
const event = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
const cb = document.getElementById("checkbox");
const cancelled = !cb.dispatchEvent(event);
if (cancelled) {
// A handler called preventDefault.
alert("cancelled");
} else {
// None of the handlers called preventDefault.
alert("not cancelled");
}
}
2、事件处理(概述)
事件是在浏览器窗口内触发的信号,用于通知浏览器或操作系统环境中的更改。程序员可以创建在事件触发时运行的事件处理程序(event handler
)代码,从而允许网页对变化做出适当的响应。
本页提供了一个关于如何使用事件和事件处理程序的非常简短的“提醒”。新开发者应该阅读事件简介。
2.1 可用的事件有哪些?
事件记录在发出事件的JavaScript对象的页面中或页面下方。例如,要查找浏览器窗口或当前文档上触发的事件,请参阅“窗口”和“文档”中的“事件”部分。
你可以使用 Event 参考来查找哪些JavaScript对象为特定的 APIs 触发事件,例如动画、媒体等等。
2.2 注册事件处理程序
有两种推荐的方法来注册处理程序。事件处理程序代码可以在触发事件时运行,方法是将其分配给目标元素对应的onevent
属性,或者使用addEventListener()
方法将处理程序注册为元素的监听器。在任何一种情况下,处理程序都将接收一个符合Event
接口(或派生接口)的对象。主要区别在于可以使用事件监听器方法添加(或删除)多个事件处理程序。
警告:不推荐使用HTML
onevent
属性设置事件处理程序的第三种方法!它们使标记膨胀,使其可读性降低,更难调试。有关更多信息,请参阅内联事件处理程序。
2.2.1 使用 onevent 属性
按照约定,触发事件的JavaScript对象具有相应的“onevent
”属性(通过在事件名称前加上前缀“on
”来命名)。在触发事件时调用这些属性以运行相关的处理程序代码,也可以由您自己的代码直接调用这些属性。
要设置事件处理程序代码,只需将其分配给适当的onevent
属性。元素中的每个事件只能分配一个事件处理程序
。如果需要,可以通过将另一个函数分配给同一属性来替换处理程序。
下面我们将展示如何使用onclick
属性为click
事件设置一个简单的greet()
函数。
const btn = document.querySelector("button");
function greet(event) {
console.log("greet:", event);
}
btn.onclick = greet;
**请注意,将表示事件的对象作为第一个参数传递给事件处理程序。**此事件对象实现Event
接口或从Event
接口派生。
2.2.2 EventTarget.addEventListener
在元素上设置事件处理程序的最灵活的方法是使用EventTarget.addEventListener
方法。这种方法允许将多个监听器分配给一个元素,并在需要时删除侦听器(使用EventTarget.removeEventListener
)。
注意:添加和删除事件处理程序的功能允许您,例如,让相同的按钮在不同的情况下执行不同的操作。此外,在更复杂的程序中,清理旧的/未使用的事件处理程序可以提高效率。
下面我们将展示如何将一个简单的greet()
函数设置为click
事件的监听器/事件处理程序(如果需要,可以使用lambda函数而不是命名函数)。再次注意,事件作为第一个参数传递给事件处理程序。
const btn = document.querySelector("button");
function greet(event) {
console.log("greet:", event);
}
btn.addEventListener("click", greet);
该方法还可以使用其他参数/选项来控制捕获和删除事件的方式。更多信息可以在EventTarget.addEventListener引用页上找到。
2.2.3 使用中止信号
一个值得注意的事件监听器特性是能够使用中止信号同时清理多个事件处理程序。
这是通过将相同的AbortSignal
传递给您希望能够一起删除的所有事件处理程序的addEventListener()
调用来完成的。然后,您可以在拥有AbortSignal
的控制器上调用abort()
,它将删除与该信号一起添加的所有事件处理程序。例如,要添加一个可以用AbortSignal
删除的事件处理程序:
const controller = new AbortController();
btn.addEventListener(
"click",
(event) => {
console.log("greet:", event);
},
{ signal: controller.signal },
); // pass an AbortSignal to this handler
然后,上面代码创建的事件处理程序可以像这样删除:
controller.abort(); // removes any/all event handlers associated with this controller
3、 js 构建块(事件介绍)
事件是在您正在编程的系统中发生的事情,系统会告诉您有关这些事件的信息,以便您的代码能够对它们做出反应。
例如,如果用户单击网页上的一个按钮,您可能希望通过显示一个信息框来对该操作作出反应。在本文中,我们将讨论与事件相关的一些重要概念,并研究它们在浏览器中的工作方式。这不会是一个详尽的研究;这就是你现在需要知道的。
3.1 什么是事件?
事件是在您正在编程的系统中发生的事情—当事件发生时,系统产生(或“触发”,produces (or “fires”))某种类型的信号,并提供一种机制,通过该机制,可以在事件发生时自动采取操作(即运行某些代码)。事件在浏览器窗口内触发,并且倾向于附加到驻留在其中的特定项。这可能是单个元素、一组元素、当前选项卡中加载的HTML文档或整个浏览器窗口。可能发生许多不同类型的事件。
例如:
- 用户选择、单击或将光标悬停在某个元素上。
- 用户在键盘上选择一个键。
- 用户调整或关闭浏览器窗口。
- 网页加载完成。
- 提交表单。
- 视频开始播放、暂停或结束。
- 出现错误。
您可以从这里(以及浏览MDN事件引用)了解到,有很多事件可以被触发。
要对事件作出反应,需要将事件处理程序(event handler
)附加到事件上。这是在事件触发时运行的代码块(通常是程序员创建的JavaScript函数)。当这样的代码块被定义为响应一个事件而运行时,我们说我们正在注册一个事件处理程序( registering an event handler
)。注意:事件处理程序有时被称为事件监听器(event listeners
)——它们在我们的目的中几乎是可互换的,尽管严格地说,它们是一起工作的。监听器监听事件的发生,处理程序是响应事件发生而运行的代码。
注意:Web事件不是JavaScript核心语言的一部分——它们被定义为浏览器内置APIs 的一部分。
示例:处理单击事件
在下面的例子中,我们在页面中只有一个<button>
:
<button>Change color</button>
然后我们有一些JavaScript。我们将在下一节中更详细地讨论这个问题,但现在我们只能说:它为按钮的“click
”事件添加了一个事件处理程序,并且处理程序通过将页面背景设置为随机颜色来响应事件:
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;
});
示例输出如下。试着点击这个按钮:
3.2 使用addEventListener ()
正如我们在上一个示例中看到的,可以触发事件的对象有一个addEventListener()方法,这是添加事件处理程序的推荐机制。
让我们仔细看看最后一个例子中的代码:
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"
,表示我们想要监听click事件。按钮可以触发许多其他事件,例如当用户将鼠标移动到按钮上时发生"mouseover",或者当用户按下一个键并且按钮定焦时发生“keydown”。 - 当事件发生时调用的函数。在我们的示例中,该函数生成一个随机的RGB颜色,并将页面
<body>
的背景色background-color设置为该颜色。
让handler函数成为一个单独的命名函数是可以的,像这样:
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
更改为以下不同的值,并观察示例中的结果:
focus and blur
-当按钮聚焦和未聚焦时,颜色会发生变化;尝试按TAB键聚焦在按钮上,然后再按TAB键将焦点从按钮移开。这些通常用于在表单字段聚焦时显示有关填充表单字段的信息,或者在表单字段填充了不正确的值时显示错误消息。- dblclick 只有双击按钮时颜色才会改变。
- mouseover and mouseout 当鼠标指针悬停在按钮上或指针离开按钮时,颜色分别发生变化。
有些事件(如click
)几乎可以在任何元素上使用。另一些则更具体,只在某些情况下有用:例如,play事件只对某些元素可用,如<video>
。
删除监听器
如果您使用addEventListener()
添加了一个事件处理程序,您可以使用removeEventListener()方法再次删除它。例如,这将删除changeBackground()
事件处理程序:
btn.removeEventListener("click", changeBackground);
事件处理程序也可以通过将AbortSignal传递给addEventListener(),然后在拥有AbortSignal
的控制器上调用abort()来删除。例如,要添加一个可以用AbortSignal删除的事件处理程序:
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
);
然后,上面代码创建的事件处理程序可以像这样删除:
controller.abort(); // removes any/all event handlers associated with this controller
对于简单的小程序,清理旧的、未使用的事件处理程序是没有必要的,但是对于更大、更复杂的程序,它可以提高效率。此外,删除事件处理程序的功能允许您让相同的按钮在不同的情况下执行不同的操作:您所要做的就是添加或删除处理程序。
为单个事件添加多个监听器
通过对addEventListener()
进行多个调用,提供不同的处理程序,您可以为单个事件提供多个处理程序:
myElement.addEventListener("click", functionA);
myElement.addEventListener("click", functionB);
现在,当单击元素时,这两个函数都将运行。
Learn more
addEventListener()
还有其他强大的特性和选项可用。
这些内容超出了本文的范围,但是如果您想阅读它们,请访问addEventListener()和removeEventListener()参考页面。
3.3 其他事件监听器机制
我们建议您使用addEventListener()
来注册事件处理程序。它是最强大的方法,适用于更复杂的程序。但是,您可能还会看到另外两种注册事件处理程序的方式:事件处理程序属性(event handler properties
)和内联事件处理程序(inline event handlers
)。
事件处理程序属性
可以触发事件的对象(如按钮)通常也具有名称为on
的属性,后面跟着事件名称。例如,元素有一个onclick
属性。这称为事件处理程序属性。要监听事件,可以将处理函数分配给属性。
例如,我们可以这样重写随机颜色的例子:
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;
};
你也可以将handler属性设置为一个命名函数:
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)
,并在第二个参数中指定不同的函数:
element.addEventListener("click", function1);
element.addEventListener("click", function2);
这对于事件处理程序属性是不可能的,因为任何后续尝试设置该属性都会覆盖先前的属性:
element.onclick = function1;
element.onclick = function2;
内联事件处理程序——不要使用这些
你可能也会在你的代码中看到这样的模式:
<button onclick="bgChange()">Press me</button>
function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}
在Web上注册事件处理程序的最早方法涉及事件处理程序HTML属性(或内联事件处理程序),如上面所示—属性值实际上是您希望在事件发生时运行的JavaScript代码。上面的例子调用了在同一页面的<script>
元素中定义的函数,但是你也可以直接在属性中插入JavaScript,例如:
<button onclick="alert('Hello, this is my old-fashioned event handler!');">
Press me
</button>
你可以找到许多事件处理程序属性的对等HTML属性;然而,你不应该使用这些——它们被认为是不好的做法。如果您正在快速执行某些操作,那么使用事件处理程序属性似乎很容易,但是它们很快就会变得难以管理且效率低下。
首先,将HTML和JavaScript混在一起并不是一个好主意,因为这会变得难以阅读。保持JavaScript独立是一种很好的做法,如果它位于单独的文件中,则可以将其应用于多个HTML文档。
即使在单个文件中,内联事件处理程序也不是一个好主意。一个按钮是可以的,但是如果你有100个按钮呢?你必须在文件中添加100个属性;它很快就会变成一场维修噩梦。使用JavaScript,你可以很容易地为页面上的所有按钮添加事件处理函数,无论有多少按钮,使用如下代码:
const buttons = document.querySelectorAll("button");
for (const button of buttons) {
button.addEventListener("click", bgChange);
}
最后,作为一种安全措施,许多常见的服务器配置将不允许内联JavaScript。
永远不要使用HTML事件处理程序属性——这些属性已经过时了,使用它们是不好的做法。
3.4 Event 对象
有时,在事件处理程序函数中,您会看到一个指定了名称的参数,如event
、evt
或e
。这称为事件对象(event object)
,它被自动传递给事件处理程序,以提供额外的功能和信息。例如,让我们再重写一下随机颜色的例子:
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
属性始终是对发生事件的元素的引用。因此,在这个例子中,我们在按钮上设置一个随机的背景颜色,而不是页面。
注意:请参阅下面的事件委托部分,以获取我们使用
event.target
的示例。
注意:您可以为事件对象使用任何您喜欢的名称—您只需要选择一个您可以在事件处理程序函数中引用它的名称。
e /evt/event
最常被开发人员使用,因为它们简短且易于记忆。保持一致总是好的——对自己,如果可能的话对别人。
事件对象的额外属性
大多数事件对象在事件对象上都有一组标准的属性和方法;有关完整列表,请参阅Event对象参考。
一些事件对象添加了与特定类型的事件相关的额外属性。例如,当用户按下一个键时触发keydown事件。它的事件对象是一个KeyboardEvent,它是一个特殊的事件对象,带有一个key
属性,告诉你按下了哪个键:
<input id="textBox" type="text" />
<div id="output"></div>
const textBox = document.querySelector("#textBox");
const output = document.querySelector("#output");
textBox.addEventListener("keydown", (event) => {
output.textContent = `You pressed "${event.key}".`;
});
3.5 防止默认行为
有时,您会遇到这样一种情况:您希望阻止事件执行其默认执行的操作。最常见的例子是web表单,例如自定义注册表单。当您填写详细信息并单击提交按钮时,自然的行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面(如果未指定另一个页面,则为同一页面)。
当用户没有正确提交数据时,麻烦就来了——作为开发人员,您希望阻止向服务器提交数据,并给出一个错误消息,说明哪里出错了,需要做些什么来纠正错误。有些浏览器支持自动表单数据验证功能,但由于许多浏览器不支持,因此建议您不要依赖这些功能,而是实现自己的验证检查。让我们看一个简单的例子。
首先是一个简单的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()函数——它会停止表单提交——然后在表单下面的段落中显示一个错误消息,告诉用户哪里出错了:
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-validate .html。
3.6 事件冒泡
事件冒泡(Event bubbling
)描述了浏览器如何处理针对嵌套元素的事件。
3.6.1 在父元素上设置监听器
考虑这样一个网页:
<div id="container">
<button>Click me!</button>
</div>
<pre id="output"></pre>
这里按钮位于另一个元素<div>
元素中。我们说这里的<div>
元素是它所包含元素的父元素。如果我们向父类添加click事件处理程序,然后单击按钮,会发生什么情况?
const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`;
}
const container = document.querySelector("#container");
container.addEventListener("click", handleClick);
你会看到,当用户单击按钮时,父类会触发一个click事件:
这是有道理的:按钮位于<div>
内部,因此当您单击按钮时,您也隐式地单击了它所在的元素。
3.6.2 冒泡的例子
如果我们向按钮和父控件添加事件侦听器,会发生什么?
<body>
<div id="container">
<button>Click me!</button>
</div>
<pre id="output"></pre>
</body>
让我们尝试将click
事件处理程序添加到按钮、它的父元素(<div>
)和包含它们的<body>
元素中:
const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`;
}
const container = document.querySelector("#container");
const button = document.querySelector("button");
document.body.addEventListener("click", handleClick);
container.addEventListener("click", handleClick);
button.addEventListener("click", handleClick);
您将看到,当用户单击按钮时,这三个元素都会触发一个click
事件:
You clicked on a BUTTON element
You clicked on a DIV element
You clicked on a BODY element
在这种情况下:
- 首先触发按钮上的单击
- 然后是其父元素(
<div>
元素)的单击 - 然后是
<div>
元素的父元素(<body>
元素)。
我们将其描述为事件从被点击的最内层元素冒出来( bubbles up
)。
这种行为可能很有用,但也可能导致意想不到的问题。在接下来的部分中,我们将看到它引起的一个问题,并找到解决方案。
3.6.3 视频播放器示例
在这个例子中,我们的页面包含一个视频,它最初是隐藏的,还有一个标签为“显示视频”的按钮。我们需要以下交互:
- 当用户点击“显示视频”按钮时,显示包含视频的框,但还没有开始播放视频。
- 当用户点击视频时,开始播放视频。
- 当用户点击视频外框中的任何位置时,隐藏该框。
HTML看起来是这样的:
<button>Display video</button>
<div class="hidden">
<video>
<source
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm"
type="video/webm" />
<p>
Your browser doesn't support HTML video. Here is a
<a href="rabbit320.mp4">link to the video</a> instead.
</p>
</video>
</div>
它包括:
<button>
元素- 一个最初具有
class="hidden"
属性的<div>
元素 - 嵌套在
<div>
元素中的<video>
元素。
我们使用CSS来隐藏带有"hidden"
类集的元素。
const btn = document.querySelector("button");
const box = document.querySelector("div");
const video = document.querySelector("video");
btn.addEventListener("click", () => box.classList.remove("hidden"));
video.addEventListener("click", () => video.play());
box.addEventListener("click", () => box.classList.add("hidden"));
您应该看到,当您单击该按钮时,将显示该框及其包含的视频。但是当你点击视频时,视频开始播放,但是盒子又被隐藏了!
视频位于<div>
内部——它是<div>
的一部分——因此单击视频会同时运行两个事件处理程序,从而导致这种行为。
3.6.4 使用stopPropagation()修复问题
正如我们在上一节中看到的,事件冒泡有时会产生问题,但是有一种方法可以防止它。Event
对象上有一个名为stopPropagation()
的函数,当在事件处理程序中调用该函数时,可以防止事件冒泡到任何其他元素。
我们可以通过修改JavaScript来解决当前的问题:
const btn = document.querySelector("button");
const box = document.querySelector("div");
const video = document.querySelector("video");
btn.addEventListener("click", () => box.classList.remove("hidden"));
video.addEventListener("click", (event) => {
event.stopPropagation();
video.play();
});
box.addEventListener("click", () => box.classList.add("hidden"));
我们在这里所做的只是在<video>
元素的’click’事件的处理程序中对事件对象调用stopPropagation()
。这将阻止事件冒泡到盒子中。现在试着点击按钮,然后点击视频:
3.6.5 事件捕获
事件传播的另一种形式是事件捕获( event capture
)。这类似于事件冒泡,但顺序相反:事件不是首先在最内层的目标元素上触发,然后依次在嵌套较少的元素上触发,事件首先在嵌套最少(least nested
)的元素上触发,然后依次在嵌套较多的元素上触发,直到到达目标。
默认情况下,事件捕获是禁用的。要启用它,必须在addEventListener()
中传递capture
选项。
这个例子就像我们之前看到的冒泡的例子,除了我们使用了capture
选项:
<body>
<div id="container">
<button>Click me!</button>
</div>
<pre id="output"></pre>
</body>
const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`;
}
const container = document.querySelector("#container");
const button = document.querySelector("button");
document.body.addEventListener("click", handleClick, { capture: true });
container.addEventListener("click", handleClick, { capture: true });
button.addEventListener("click", handleClick);
在这种情况下,消息的顺序是颠倒的:<body>
事件处理程序首先触发,然后是<div>
事件处理程序,然后是<button>
事件处理程序:
为什么要同时捕获和冒泡呢?在糟糕的过去,浏览器的交叉兼容性远不如现在,Netscape只使用事件捕获,Internet Explorer只使用事件冒泡。当W3C决定尝试标准化行为并达成共识时,他们最终采用了包含这两者的系统,这就是现代浏览器实现的。
默认情况下,几乎所有事件处理程序都注册在冒泡阶段,这在大多数情况下更有意义。
3.7 事件委托
在上一节中,我们研究了由事件冒泡引起的问题以及如何修复它。不过,事件冒泡并不只是令人讨厌:它可以非常有用。特别是,它支持事件委托(event delegation
)。在这个实践中,当我们希望某些代码在用户与大量子元素中的任何一个交互时运行时,我们在它们的父元素上设置事件监听器,并让发生在它们身上的事件冒泡到它们的父元素上,而不必在每个子元素上单独设置事件侦听器。
让我们回到我们的第一个例子,当用户点击一个按钮时,我们设置整个页面的背景颜色。假设,页面被划分为16块区域,当用户点击那个区域时,我们想要设置每个瓷砖的随机颜色。
<div id="container">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>
我们有一个小小的CSS,用来设置贴图的大小和位置:
.tile {
height: 100px;
width: 25%;
float: left;
}
现在在JavaScript中,我们可以为每个tile添加一个click事件处理程序。但一个更简单、更有效的选择是在父类上设置click事件处理程序,并依靠事件冒泡来确保当用户单击tile时处理程序被执行:
function random(number) {
return Math.floor(Math.random() * number);
}
function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
return rndCol;
}
const container = document.querySelector("#container");
container.addEventListener("click", (event) => {
event.target.style.backgroundColor = bgChange();
});
注意:在本例中,我们使用
event.target
获取作为事件目标的元素(即最内层元素)。如果我们想要访问处理该事件的元素(在本例中是容器),我们可以使用event.currentTarget
。
注意:完整的源代码见useful-eventtarget.html;也可以在这里看到它的现场运行。
不仅仅是网页
事件并不是JavaScript独有的——大多数编程语言都有某种事件模型,而模型的工作方式通常与JavaScript的方式不同。事实上,用于网页的JavaScript事件模型不同于用于其他环境的JavaScript事件模型。
例如,Node.js是一个非常流行的JavaScript运行时,它使开发人员能够使用JavaScript构建网络和服务器端应用程序。Node.js事件模型依赖于监听器来监听事件,依赖于发射器来周期性地发出事件——听起来没有什么不同,但代码却大不相同,使用on()
等函数来注册事件监听器,使用once()
来注册事件监听器,该事件监听器在运行一次后取消注册。HTTP连接事件文档提供了一个很好的例子。
你也可以使用JavaScript构建跨浏览器的插件——使用一种叫做WebExtensions的技术来增强浏览器的功能。事件模型类似于web事件模型,但有一点不同——事件侦听器的属性是以驼峰形式编写的(例如onMessage
而不是onmessage
),并且需要与addListener
函数结合使用。查看runtime.onMessage页面例子。
在学习的这个阶段,你不需要了解其他类似的环境;我们只是想说明事件在不同的编程环境中是不同的。
See also
- Event reference
- Event order (关于捕获和冒泡的讨论)——Peter-Paul Koch写的一篇非常详细的文章。
- 事件访问 (关于事件对象的讨论)——彼得-保罗·科赫的另一篇非常详细的文章
- domevents.dev 一个非常有用的交互式游乐场应用程序,可以通过探索来学习DOM事件系统的行为。