JavaScript编程
带有用户界面的应用程序——以及其他类型的应用程序——主要由事件驱动。在这里,我们专注于 DOM 事件。它们来自于浏览器或本地应用程序中的特定操作或情况,例如,用户点击鼠标、按下键盘键、或在触摸屏上操作;视觉对象被“拖放”、“复制粘贴”或调整大小;HTML 页面加载或卸载;浏览器或视觉对象获取或失去焦点等。应用程序还可以通过编程方式创建事件(调度)。
事件与其源对象相关,并与 JavaScript 语句关联;通常,这是对一个函数的调用,该函数被称为事件处理程序。JavaScript 部分在事件发生后被调用。常见的操作包括与服务器的通信、用户输入的验证、或修改 DOM 或图形。
创建和触发事件
嵌入在 HTML 中
一个简短的示例展示了如何在 HTML 页面中定义事件、触发事件并执行操作。这种语法版本被称为内联 JavaScript。
<!DOCTYPE html>
<html>
<head>
<script>
function handleEvent(evt) {
"use strict";
alert("通过 JavaScript 函数执行一些操作。");
}
</script>
</head>
<body id="body" style="margin:2em">
<!-- 内联所有语句;不常见 -->
<button onclick="const msg='直接调用小操作。'; alert(msg);">第一个按钮</button>
<!-- 调用函数(事件处理程序) -->
<button onclick="handleEvent(event)">第二个按钮</button>
</body>
</html>
当用户点击其中一个按钮时,浏览器会读取按钮的 onclick
属性,创建一个包含事件所有属性的 JavaScript 对象,并执行相应的 JavaScript 语句。通常,这部分 JavaScript 代码是对一个函数的调用,这个函数被称为事件处理程序。该函数接收 JavaScript 对象作为其第一个参数。只有在非常简单的情况下,完整的 JavaScript 脚本才会内联。
当然,调用的函数必须在某处存在。一些标准函数,如 alert()
,是由浏览器预定义并提供的。你自己编写的函数存在于 HTML 标签 <script>
中,或者该标签引用一个文件或 URL,指向函数所在的地方。
提示:将事件定义直接嵌入 HTML 中被称为内联 JavaScript。这是注册事件处理程序的最早方法,但它倾向于使源代码在非简单应用中难以阅读。因此,它可以被认为比其他非干扰性技术(见下一章)不太理想。内联 JavaScript 的使用与使用内联 CSS 类似,即通过将 CSS 放入 style
属性中来为 HTML 添加样式。
尽管如此,本书将在演示页面中经常使用内联 JavaScript,因为语法简洁,概念易于理解。
通过 JavaScript 编程
JavaScript 有两种方式来为 HTML 元素注册事件处理程序。首先,事件处理函数可以直接赋值给元素的属性 onxxx
(如 onclick
, onkeydown
, onload
, onfocus
等)。它们的名称以 on
开头,后跟事件类型的值。其次,可以使用 addEventListener
函数来注册事件类型和事件处理程序。
<!DOCTYPE html>
<html>
<head>
<script>
function register() {
"use strict";
const p1 = document.getElementById("p1");
// 这样做...
p1.onclick = showMessage; // 不加括号
// 或者这样做(推荐)
// p1.addEventListener('click', showMessage);
alert("事件处理程序已分配给段落。");
}
function showMessage(evt) {
"use strict";
// 参数 'evt' 包含关于事件的许多信息,
// 例如,它起源的位置信息
alert("点击事件到段落。位置为 " + evt.clientX + " / " + evt.clientY + "。事件类型是:" + evt.type);
}
</script>
</head>
<body>
<h1>注册事件</h1>
<p id="p1" style="margin:2em; background-color:aqua">
一个小段落。先没有事件处理程序,然后有一个事件处理程序。
</p>
<button onclick="register()" id="button_1">一个按钮</button>
</body>
</html>
按钮 button_1
的 onclick
事件处理程序 register
通过上述内联 JavaScript 语法注册。当页面加载时,仅知道这个事件处理程序。点击段落 p1
不会触发任何操作,因为它尚未有事件处理程序。但是,当按钮被按下时,按钮 button_1
的事件处理程序会注册第二个事件处理程序,即函数 showMessage
。现在,在段落上点击后,会弹出 "点击事件到段落..." 的提示框。
注册发生在第 10 行:p1.onclick = showMessage
。与内联 JavaScript 的显著区别在于这里没有使用括号。内联 JavaScript 会调用函数 showMessage
,因此需要使用括号。而 register
函数并没有调用 showMessage
,它只是使用了函数名来完成注册过程。
使用 addEventListener
函数是为段落注册事件的另一种方法。它作用于元素 p1
并接受两个参数。第一个参数是事件类型(如 click
, keydown
等),这些事件类型与 onxxx
名称相关,只是缺少前两个字符 on
。第二个参数是作为事件处理程序的函数名称。
你可以通过注释掉第 10 行或第 12 行来测试这个例子。第 12 行使用的 addEventListener
是推荐的版本。
事件类型
根据原始元素的类型,存在不同种类的事件。完整的事件类型列表非常庞大(参考 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";
// 注册不同的事件类型
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("来自元素的事件发生: " +
evt.target.id + ". 事件类型是: " + evt.type);
}
</script>
</head>
<body onload="registerAllEvents()" style="margin:1em">
<h1 id="h1" style="background-color:aqua">检查事件</h1>
<p id="p1" style="background-color:aqua">一个小段落。(点击)</p>
<p id="p2" style="background-color:aqua">一个小段落。(双击)</p>
<p id="p3" style="background-color:aqua">一个小段落。(鼠标悬停)</p>
<p style="background-color:aqua">
<textarea id="t1" rows="1" cols="50">(按键)</textarea>
</p>
<p style="background-color:aqua">
<textarea id="t2" rows="1" cols="50">(选择)</textarea>
</p>
<button id="button_1">一个按钮(鼠标悬停)</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">检查事件</h1>
<p id="p1" style="margin:2em; background-color:aqua">一个小段落。</p>
<p id="p2" style="margin:2em; background-color:aqua">另一个小段落。</p>
<button id="button_1">按钮</button>
</body>
</html>
这里给 body
注册了一个 mousemove
事件。每当鼠标在页面中移动时,事件会被触发。事件处理程序从 JavaScript 对象中读取 x
和 y
属性,并将其显示在浏览器标签页的标题中。
removeEventListener
与 addEventListener
类似,removeEventListener
函数可以从元素中移除事件监听器。
合成事件
系统提供了上述展示的丰富事件类型。此外,你还可以创建自己的事件并从应用程序中触发它们。
首先,你需要创建一个包含事件处理程序的函数。接着,使用 addEventListener
将该事件处理程序注册到某个元素上。到目前为止,这与使用预定义的事件类型是相同的。
function register() {
"use strict";
// ...
// 选择一个任意的事件类型('addEventListener' 的第一个参数)
// 并在元素上注册函数
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// 非系统事件的事件处理程序
function f(evt) {
alert("合成事件在元素 " + evt.target.id + " 上被调用,事件类型是: " + evt.type);
}
现在,你可以在应用程序的任何部分触发此事件。为此,你需要创建一个新事件,指定正确的事件类型,并通过 dispatchEvent
方法触发它。
function triggerEvent(evt) {
"use strict";
// 创建一个新的事件,指定适当的类型
const newEvent = new Event("syntheticEvent");
// 在元素 '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);
// 选择一个任意的事件类型('addEventListener' 的第一个参数)
// 并在元素上注册函数
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// 非系统事件的事件处理程序
function f(evt) {
"use strict";
alert("合成事件在元素 " + evt.target.id + " 上被调用,事件类型是: " + evt.type);
}
function showMessage(evt) {
"use strict";
// 'evt' 参数包含关于事件的许多信息,
// 例如,它的发生位置
alert("点击事件发生在元素: " + evt.target.id + ",事件类型是: " + evt.type);
}
function triggerEvent(evt) {
"use strict";
// 创建一个新的事件,指定适当的类型
const newEvent = new Event("syntheticEvent");
// 在元素 'p1' 上触发此事件
document.getElementById("p1").dispatchEvent(newEvent);
}
</script>
</head>
<body onload="register()">
<h1>以编程方式创建事件。</h1>
<p id="p1" style="margin:2em; background-color:aqua">一个小段落。</p>
<p id="p2" style="margin:2em; background-color:aqua">另一个小段落。</p>
<button id="button_1">按钮</button>
</body>
</html>
在开始时,按钮监听 click
事件,而 p1
既监听 click
类型的事件也监听 syntheticEvent
类型的事件。当点击按钮时,它的事件处理程序 triggerEvent
创建了一个新的 syntheticEvent
类型事件,并在 p1
上触发它(这是此示例的主要目的)。事件处理程序 showMessage
显示一条信息,而无需点击 p1
。换句话说:p1
上的事件在没有点击 p1
的情况下被触发。
使用 CustomEvent
传递数据
如果你需要在事件处理程序中获取一些来自调用函数的数据(例如,错误信息文本、HTTP 响应数据等),你可以通过使用 CustomEvent
和其 detail
属性来传递这些数据:
const newEvent = new CustomEvent("syntheticEvent", {detail: "一条简短的消息。"});
function f(evt) {
"use strict";
alert("合成事件在元素 " + evt.target.id + " 上被调用,事件类型是: " + evt.type + ". " + evt.detail);
}
(A)同步行为
大多数事件是同步处理的,例如 click
、key
或 mouse
事件。但也有一些例外是异步处理的,例如 load
事件。同步
意味着调用顺序与它们创建的顺序相同。例如,按顺序点击按钮 A、B、C 将依次触发 A、B、C 的事件处理程序。相对而言,异步
事件可能会导致相关处理程序以任意顺序被调用。
需要区分的是,事件处理程序的调用顺序与其实现的方式。每种实现都可以是严格顺序的,也可以包含异步调用——这取决于你的意图。典型的异步调用包括 HTTP 请求或数据库查询。当然,这些也可以是点击事件处理程序的一部分。