JavaScript/处理 DOM 事件
具有用户界面的应用程序(以及其他应用程序类型)主要由事件驱动。在这里,我们重点介绍 DOM 事件。它们起源于(或触发)浏览器或原生应用程序中的特定操作或情况,例如用户用鼠标点击、输入键盘键或在触摸屏上操作;视觉对象被“拖放”、“复制粘贴”或调整大小;HTML 页面加载或要卸载;浏览器或视觉对象获得或失去焦点;等等。应用程序也可以以编程方式创建事件(分派)。
事件与其起源对象和 JavaScript 语句相关联;也就是说,在大多数情况下,它调用一个被称为事件处理程序的函数。JavaScript 部分在事件发生后被调用。常见的操作包括与服务器通信、验证用户输入或修改 DOM 或图形。
一个简短的例子展示了事件如何在 HTML 页面中定义、触发和执行。这种语法版本被称为内联 JavaScript。
<!DOCTYPE html>
<html>
<head>
<script>
function handleEvent(evt) {
"use strict";
alert("Perform some actions via a JavaScript function.");
}
</script>
</head>
<body id="body" style="margin:2em">
<!-- inline all statements; unusual -->
<button onclick="const msg='Direct invocation of small actions.'; alert(msg);">First button</button>
<!-- call a function (the event handler) -->
<button onclick="handleEvent(event)">Second button</button>
</body>
</html>
当用户点击其中一个按钮时,浏览器会读取按钮的属性onclick
,创建一个包含事件所有属性的 JavaScript 对象,并执行 JavaScript 语句。大多数情况下,JavaScript 部分是对一个函数的调用。该函数被称为事件处理程序。它接收 JavaScript 对象作为其第一个参数。只有在非常简单的情况下,完整的 JavaScript 脚本才被内联。
当然,被调用的函数必须存在于某个地方。一些标准函数,例如alert()
,是预定义的,由浏览器提供。你自写的函数存在于 HTML 标签<script>
中,或者该标签引用了它们存在的文件或 URL。
提示:将事件定义直接嵌入 HTML 被称为内联 JavaScript。这是注册事件处理程序的最早方法,但对于非平凡的应用程序,它往往会使源代码难以阅读。因此,它可以被认为是一种比其他非侵入式技术更不 理想的技术;参见下一章。使用内联 JavaScript 的方式可以认为与使用内联 CSS 的方式类似,其中 HTML 通过将 CSS 放入style
属性来设置样式。
然而,这本书将在其演示页面中经常使用内联 JavaScript,因为其语法简洁,概念易于理解。
JavaScript 知道两种为 HTML 元素注册事件处理程序的方法。首先,事件处理程序函数可以直接分配给元素的属性onxxx
(onclick、onkeydown、onload、onfocus、...)。它们的名称以 'on' 开头,以事件类型的值结尾。其次,函数addEventListener
注册事件类型和事件处理程序。
<!DOCTYPE html>
<html>
<head>
<script>
function register() {
"use strict";
const p1 = document.getElementById("p1");
// do it this way ...
p1.onclick = showMessage; // no parenthesis ()
// ... or this way (prefered)
//p1.addEventListener('click', showMessage);
alert("The event handler is assigned to the paragraph.");
}
function showMessage(evt) {
"use strict";
// the parameter 'evt' contains many information about
// the event, e.g., the position from where it originates
alert("A click-event to the paragraph. At position " +
evt.clientX + " / " + evt.clientY + ". Event type is: " + evt.type);
}
</script>
</head>
<body>
<h1>Register an event</h1>
<p id="p1" style="margin:2em; background-color:aqua">
A small paragraph. First without, then with an event handler.
</p>
<button onclick="register()" id="button_1">A button</button>
</body>
</html>
按钮 'button_1' 的 onclick 事件处理程序 'register' 是使用上面的内联 JavaScript 语法注册的。当页面加载时,只有这个事件处理程序是已知的。点击段落 'p1' 不会触发任何操作,因为它到目前为止还没有任何事件处理程序。但是,当按钮被按下时,按钮 'button_1' 的处理程序会注册第二个事件处理程序,即函数 'showMessage'。现在,在点击段落后,就会出现警报“对段落的点击事件...”。
注册在第 10 行完成p1.onclick = showMessage
。与内联 JavaScript 的明显区别在于没有括号。内联 JavaScript 调用函数showMessage
,因此需要使用括号。函数register
不会调用showMessage
。它只在注册过程中使用其名称。
使用函数addEventListener
是将函数分配给段落的 'onclick' 属性的另一种方法。它作用于元素 'p1' 并接收两个参数。第一个是事件类型(click、keydown、...)。这些事件类型与onxxx 名称相对应,只是缺少前两个字符 'on'。第二个参数是充当事件处理程序的函数的名称。
你可以通过注释掉第 10 行或第 12 行来测试示例。使用addEventListener
函数的第 12 行是首选版本。
不同的事件类型取决于起源元素的类型。事件类型的完整列表非常庞大 (MDN) (W3schools)。我们展示了一些重要的类型和示例。
名称 | 描述 |
---|---|
blur
|
输入元素失去焦点 |
change
|
元素被修改 |
click
|
元素被点击 |
dblclick
|
元素被双击 |
error
|
加载元素时发生错误 |
focus
|
输入元素获得焦点 |
keydown
|
当元素有焦点时,按下了一个键 |
keyup
|
当元素有焦点时,释放了一个键 |
load
|
元素被加载 |
mouseenter
|
鼠标指针移动到元素内 |
mousemove
|
鼠标指针在元素内部移动 |
mousedown
|
鼠标按钮在元素上按下 |
mouseup
|
鼠标按钮在元素上释放 |
mouseleave
|
鼠标指针从元素中移出 |
reset
|
表单的重置按钮被点击 |
resize
|
包含窗口或框架被调整大小 |
select
|
元素中的部分文本被选中 |
submit
|
表单正在提交 |
unload
|
内容正在卸载(例如,窗口被关闭) |
以下示例演示了一些不同的事件类型。
<!DOCTYPE html>
<html>
<head>
<script>
function registerAllEvents() {
"use strict";
// register different event types
document.getElementById("p1").addEventListener("click", handleAllEvents);
document.getElementById("p2").addEventListener("dblclick", handleAllEvents);
document.getElementById("p3").addEventListener("mouseover", handleAllEvents);
document.getElementById("t1").addEventListener("keydown", handleAllEvents);
document.getElementById("t2").addEventListener("select", handleAllEvents);
document.getElementById("button_1").addEventListener("mouseover", handleAllEvents);
}
function handleAllEvents(evt) {
"use strict";
alert("An event occurred from element: " +
evt.target.id + ". Event type is: " + evt.type);
}
</script>
</head>
<body onload="registerAllEvents()" style="margin:1em">
<h1 id="h1" style="background-color:aqua">Check Events</h1>
<p id="p1" style="background-color:aqua">A small paragraph. (click)</p>
<p id="p2" style="background-color:aqua">A small paragraph. (double click)</p>
<p id="p3" style="background-color:aqua">A small paragraph. (mouse over)</p>
<p style="background-color:aqua">
<textarea id="t1" rows="1" cols="50">(key down)</textarea>
</p>
<p style="background-color:aqua">
<textarea id="t2" rows="1" cols="50">(select)</textarea>
</p>
<button id="button_1">A button (mouse over)</button>
</body>
</html>
当页面加载时,body
的onload 事件被触发。请注意,这里 'on' 前缀是必要的,因为它是在内联 JavaScript 语法中(第 23 行)。被调用的函数registerAllEvents 定位不同的 HTML 元素,并注册不同类型的事件处理程序(第 8 - 13 行)。通常你会注册不同的函数,但为了简单起见,在这个示例中,我们将相同的函数handleAllEvents 注册到所有元素。这个函数报告事件类型和起源 HTML 元素。
事件始终以 JavaScript 对象的形式作为其第一个参数传递给事件处理程序。JavaScript 对象由属性组成;属性是键值对。在所有情况下,其中一个键是 'type'。它包含事件的类型;它的一些可能值在上面的表格中显示。根据事件类型,还有许多其他属性可用。
以下是一些或多或少重要的属性的示例。
名称 | 描述 |
---|---|
button |
返回被点击的鼠标按钮 |
clientX |
返回鼠标指针在本地坐标系内的水平坐标:滚出部分不计入 |
clientY |
返回鼠标指针在本地坐标系内的垂直坐标:滚出部分不计入 |
code |
返回所按键的文本表示,例如,“ShiftLeft” 或 “KeyE” |
key |
返回所按键的值,例如,“E” |
offsetX |
返回鼠标指针在目标 DOM 元素内的水平坐标 |
offsetY |
返回鼠标指针在目标 DOM 元素内的垂直坐标 |
pageX |
返回鼠标指针在页面坐标系内的水平坐标 - 包括滚动超出部分 |
pageY |
返回鼠标指针在页面坐标系内的垂直坐标 - 包括滚动超出部分 |
screenX |
返回鼠标指针在完整显示器坐标系内的水平坐标 |
screenY |
返回鼠标指针在完整显示器坐标系内的垂直坐标 |
target |
返回触发事件的元素 |
timeStamp |
返回元素创建和事件创建之间的时间差(以毫秒为单位) |
type |
返回触发事件的元素类型 |
x |
clientX 的别名 |
y |
clientY 的别名 |
以下脚本展示了如何访问属性。
<!DOCTYPE html>
<html>
<head>
<script>
function changeTitle(evt) {
"use strict";
const xPos = evt.x;
const yPos = evt.y;
document.title = [xPos, yPos];
}
</script>
</head>
<body onmousemove="changeTitle(event)" style="margin:1em; border-width: 1px; border-style: solid;">
<h1 id="h1" style="background-color:aqua">Check Events</h1>
<p id="p1" style="margin:2em; background-color:aqua">A small paragraph.</p>
<p id="p2" style="margin:2em; background-color:aqua">Another small paragraph.</p>
<button id="button_1">Button</button>
</body>
</html>
为 body
注册一个鼠标移动事件。每当鼠标在 body 上移动时,就会触发该事件。事件处理程序从 JavaScript 对象中读取 x/y 属性,并在浏览器活动选项卡的标题中显示它们。
removeEventListener
[edit | edit source]类似于 addEventListener
,removeEventListener
函数从元素中移除事件监听器。
合成事件
[edit | edit source]系统提供了上述丰富的事件类型集。此外,您还可以创建自己的事件,并从您的应用程序中触发它们。
首先,创建一个带有一个参数(事件处理程序)的函数。接下来,为元素注册此事件处理程序。到目前为止,一切都与预定义事件类型相同。
function register() {
"use strict";
// ...
// choose an arbitrary event type (first parameter of 'addEventListener')
// and register function on element
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// the event handler for the non-system event
function f(evt) {
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type);
}
现在您可以在应用程序的任何部分触发此事件。为此,创建一个与所选类型完全相同的事件,并使用对 dispatchEvent
的调用来触发它。
function triggerEvent(evt) {
"use strict";
// create a new event with the appropriate type
const newEvent = new Event("syntheticEvent");
// trigger this event on element 'p1'
document.getElementById("p1").dispatchEvent(newEvent);
}
出于测试目的,我们将此功能绑定到按钮。完整页面现在看起来像这样
<!DOCTYPE html>
<html>
<head>
<script>
function register() {
"use strict";
document.getElementById("p1").addEventListener("click", showMessage);
document.getElementById("p2").addEventListener("click", showMessage);
document.getElementById("button_1").addEventListener("click", triggerEvent);
// choose an arbitrary event type (first parameter of 'addEventListener')
// and register function on element
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// the event handler for the non-system event
function f(evt) {
"use strict";
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type);
}
function showMessage(evt) {
"use strict";
// the parameter 'evt' contains many information about
// the event, e.g., the position from where it originates
alert("A click event to element: " + evt.target.id +
" The event type is: " + evt.type);
}
function triggerEvent(evt) {
"use strict";
// create a new event with the appropriate type
const newEvent = new Event("syntheticEvent");
// trigger this event on element 'p1'
document.getElementById("p1").dispatchEvent(newEvent);
}
</script>
</head>
<body onload="register()">
<h1>Create an event programmatically.</h1>
<p id="p1" style="margin:2em; background-color:aqua">A small paragraph.</p>
<p id="p2" style="margin:2em; background-color:aqua">Another small paragraph.</p>
<button id="button_1">Button</button>
</body>
</html>
在一开始,按钮会监听点击事件,而 'p1' 也会监听 'click' 类型的事件以及 'syntheticEvent' 类型的事件。当按钮被点击时,其事件处理程序 'triggerEvent' 会创建一个新的 'syntheticEvent' 类型事件,并在 'p1' 上触发它(这是此示例的主要目的)。事件处理程序 showMessage
会显示一条消息,而无需点击 'p1'。换句话说:'p1' 上的事件发生,而没有点击 'p1'。
您可能需要在事件处理程序中获得来自调用函数的一些数据,例如,错误消息的文本、HTTP 响应的数据,...。您可以使用 CustonEvent
及其属性 'detail' 传递此类数据
const newEvent = new CustomEvent("syntheticEvent", {detail: "A short message."});
在事件处理程序中访问数据
function f(evt) {
"use strict";
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type + ". " + evt.detail);
}
(A)同步行为
[edit | edit source]大多数事件以同步方式处理,例如 'click'、'key' 或 'mouse'。但有一些例外是异步处理的,例如 'load' [1] [2]。'同步' 意味着调用的顺序与其创建的顺序相同。依次点击按钮 A、B 和 C 会导致按此顺序依次调用 A、B 和 C 的事件处理程序。相反,'异步' 事件会导致相关处理程序的调用顺序任意。
您必须区分这个问题,即事件处理程序的调用,与它们主体的实现。每个实现都可以严格按顺序执行,也可以包含 异步调用 - 这取决于您的意图。典型的异步调用是 HTTP 请求或数据库查询。当然,它们可以作为点击事件处理程序的一部分。