第10章 事件
绩效
| 章节 | 代码量(行) |
|---|---|
| 10.1 | 0 |
| 10.2 | 236 |
10.1 事件驱动程序设计
在 JavaScript 中,最为重要的一件事就是对事件进行处理。与通常的 GUI 应用程序相同,Web 应用程序也是通过事件驱动程序设计的方式来实现其功能的。在事件驱动程序设计中,需要注册不同事件的处理方式。
在注册了事件的处理方式之后,浏览器就会在该事件发生时执行所注册的处理方式。所登录的处理方式被称作事件处理程序、事件句柄或事件侦听器。
对于 Web 应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等。此外,读取页面或跳转至其他页面等行为也会引发事件。根据这些不同的用户操作,浏览器会触发相应的事件。之后,执行事件处理程序,处理被触发的事件。
所以说,JavaScript 程序设计的基本内容之一就是获取需要对事件进行捕捉的元素,并针对该元素注册相应的事件处理程序。
DOM Level 2 中定义了标准的事件模型。大部分现代浏览器都是根据这一标准实现的。但是,在Internet Explorer 8 以及更早版本中,采用了自定义的事件模型实现方式。从功能上来说,这确实和标准事件模型没有太大的差别,但 API 是完全不同的,应当加以注意。
10.2 事件处理程序/事件侦听器的设定
对事件的处理方式被称为事件处理程序或事件侦听器,但这两者之间其实是有区别的。它们的设定方法并不相同,因此,两者支持的处理元素数量也不同。对于 1 个元素或事件,只能设定 1 个事件处理程序。而与之相对的,可以为其同时设定多个事件侦听器。
下面是一些对事件处理进行设定的方式。
- 指定为 HTML 元素的属性(事件处理程序)
- 指定为 DOM 元素的属性(事件处理程序)
- 通过 EventTarget.addEventListener() 进行值定(事件侦听器)
10.2.1 指定为 HTML 元素的属性
将事件处理程序指定为 HTML 元素的属性是一种最为简单的设定事件处理程序的方式。在下面的例子中,将会在发生按钮点击事件时显示含有 “黄子涵是帅哥!” 和 “黄子涵是靓仔!” 消息的提示对话框。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指定为 HTML 元素的属性</title>
</head>
<body>
<input id="hzh" type="button" value="黄子涵" onclick="console.log('黄子涵是帅哥!');
console.log('黄子涵是靓仔!')">
</body>
</html>

在这个例子中,通过字符串对 onclick 事件处理程序将要执行的 JavaScript 代码进行了设定。如果代码包含多行,则可以通过分号分隔。当然,事先另外定义一个函数之后再执行该函数的方式也不会有问题。
这种方式的优点在于,设定步骤非常简单,并且能够确保事件处理程序会在载入时被设定。而如果使用之后所介绍的一些方法则可能会产生一些问题。即元素在被载入时,其事件处理程序可能还没有被注册,这时用户执行任何本应触发事件的操作,也不会有任何效果。与之相对地,将事件处理程序指定为 HTML 元素的属性的话,就能够确保它在载入的同时被设定。
在书写上也有一些需要注意的地方,就是这里的 onclick 全都是以小写字母书写的。HTML 不会区分大小写字母,所以即使在这里使用了 onClick 也不会有什么差别。但是,XHTML 则会区分大小写字母。考虑到这点,最好还是使用全部小写的 onclick,这样将有助于提高代码的兼容性。
表 10.1 事件处理程序
| 事件处理程序名 | 触发的时机 |
|---|---|
| onclick | 鼠标点击操作 |
| ondblclick | 鼠标双击操作 |
| onmousedown | 按下了鼠标按键 |
| onmouseup | 放开了鼠标按键 |
| onmousemove | 鼠标指针在元素上方移动 |
| onmouseout | 鼠标指针从元素上方离开 |
| onmouseover | 鼠标指针移动至了元素上方 |
| onkeydown | 按下了键盘按键 |
| onkeypress | 按过了键盘按键 |
| onkeyup | 放开了键盘按键 |
| onchange | 更改了input 元素的内容 |
| onblur | input 元素失去了焦点 |
| onfocus | input 元素获得了焦点 |
| onselect | 文本被选取 |
| onsubmit | 按下了表单的提交按钮 |
| onreset | 按下了表单的重置按钮 |
| onload | 载入完成 |
| onunload | 文档的载入被撤销(例如页面跳转等情况时) |
| onabort | 图像的读取被中断 |
| onerror | 图像读取过程中发生错误 |
| onresize | 窗口尺寸发生改变 |
如果事件处理程序返回了一个 false 值,则会取消该事件的默认行为。例如,当onsubmit 事件处理程序返回了一个 false 时,表单的内容将不会被发送。这在使用 onsubmit 事件处理程序验证表单内容时会很有用,可以在发现内容有误时返回 false 以取消表单数据的发送。另外,如果像代码清单 10.1 中的例子这样,在
<a>标签的 onclick 事件处理程序中返回 false,则会取消页面的跳转。
代码清单 10.1 在事件处理程序中返回false
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在事件处理程序中返回false</title>
</head>
<body>
<a id="hzh" href="www.huangzihan.top" onclick="return stop();">黄子涵</a>
<script>
function stop(event) {
console.log("黄子涵是帅哥!");
return false;
}
</script>
</body>
</html>

10.2.2 指定为 DOM 元素的属性
如果一个页面分别使用了 HTML 文件和 JavaScript文件,则应该尽可能少地在 HTML 文件中使用JavaScript 代码,以提高可维护性。因此,最好将事件处理程序的设定全都写在 JavaScript 内。
事件处理程序可以被指定为节点的属性(代码清单10.2)。
代码清单 10.2 将事件处理程序指定为属性
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>将事件处理程序指定为属性</title>
</head>
<body>
<button id="huangzihan">黄子涵</button>
<script>
var huangzihan = document.getElementById('huangzihan');
function hzh() {
console.log("黄子涵");
}
huangzihan.onclick = hzh;
</script>
</body>
</html>

需要注意的是,这里被指定为事件处理程序的正是一个函数。像下面这样,以函数执行后的返回值或用于 HTML 标签的字符串的形式来设定的话,将会发生错误。
代码清单 10.3 事件处理程序的设定
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件处理程序的设定</title>
</head>
<body>
<button id="hzh1">hzh1</button>
<button id="hzh2">hzh2</button>
<button id="hzh3">hzh3</button>
<script>
var hzh1 = document.getElementById('hzh1');
var hzh2 = document.getElementById('hzh2');
var hzh3 = document.getElementById('hzh3');
function huangzihan() {
console.log("黄子涵");
}
hzh1.onclick = huangzihan(); // 这种方式指定的是函数执行后的返回值,是错误的
hzh2.onclick = "huangzihan()"; // 以字符串的形式指定该函数也是无效的
hzh3.onclick = huangzihan; // 将函数指定为了事件处理程序,而能够正常运行
</script>
</body>
</html>

与通过 HTML 标签的属性设定时不同,这里必须全部使用小写字母书写。
而在设定为了属性之后,HTML 标签属性中的内容将会被覆写。因此,如果希望通过 JavaScript 代码在 HTML 标签属性所指定的内容之后再追加新的处理操作,仅采用这种指定 DOM 元素的方法是很难实现的。在 DOM Level 2 Events 中定义的一种方法可以简单地解决这一问题。
10.2.3 通过 EventTarget.addEventListener() 进行指定
注册事件侦听器
虽然之前所介绍的各种方式也能够对事件注册各种各样的处理,但它们都有一个缺点,那就是对于某一个元素的某一个事件,只能够指定 1 种处理操作。
如果只能够指定 1 种处理操作的话,就很难处理复杂的行为。为了弥补这一缺点,在 DOM Level 2 中定义了 EventTarget.addEventListener() 方法(代码清单 10.4)。不过正如前面所说,该方法无法在 Internet Explorer 8 以及更早版本的浏览器中使用。为此可以在 Internet Explorer 中换用 attachEvent() 方法。
代码清单 10.4 注册事件侦听器
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册事件侦听器</title>
</head>
<body>
<button id="hzh1">hzh1</button>
<script>
var hzh1 = document.getElementById('hzh1');
hzh1.addEventListener('click', function(e) {
console.log("黄子涵");
}, false);
</script>
</body>
</html>

在注册事件侦听器时,还可以指定第 3 个参数,用以指定从捕获阶段还是从事件冒泡阶段开始执行。在 DOM Level 2 中,这一参数是必须的。而在 DOM Level 3 中,如果省略了该参数,则会默认从事件冒泡阶段开始执行。之前介绍的指定为 HTML 元素属性的方式,以及指定为 DOM 元素属性的方式,都会在事件冒泡阶段执行事件处理程序。如果希望在捕获阶段执行事件处理程序的话,则只能使用EventTarget.addEventListener() 方法了。而在 Internet Explorer 所使用的 attachEvent() 方法中,是没有与之相对应的参数的。在 Internet Explorer 中,事件侦听器总是会在事件冒泡阶段被执行。
事件侦听器的执行顺序
可以通过 addEventListener() 方法对某个特性元素的特定事件设定多个不同的事件侦听器。如果注册了多个事件侦听器,则会产生事件侦听器之间的执行顺序的问题。然而在 DOM Level 2 中并没有对这一点进行定义。在 DOM Level 3 中则是将执行顺序规定为与注册顺序相同。事实上,目前绝大部分的浏览器也都是以注册的顺序对事件侦听器执行的。即使如此,对于和执行顺序有关的处理,还是应该把它们放在同一个事件侦听器中执行,而不应该将它们置于多个不同的事件侦听器之中。
此外,不能同时对事件目标、事件类型及执行阶段都相同的对象注册多个相同的事件侦听器。之后的注册将会被忽略。在这种情况下,事件侦听器的注册顺序不会发生变化,所以其执行顺序也不会改变。
代码清单 10.5 对同一个事件侦听器进行注册
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>对同一个事件侦听器进行注册</title>
</head>
<body>
<button id="huangzihan">黄子涵</button>
<script>
var huangzihan = document.getElementById('huangzihan');
function sayHuangzihan() {
alert("你好呀!黄子涵。");
}
huangzihan.addEventListener('click', sayHuangzihan, false);
huangzihan.addEventListener('click', sayHuangzihan, false); // 对相同的事件侦听器进行注册将被忽略
huangzihan.addEventListener('click', sayHuangzihan, true);
// 由于执行阶段不同,则将会被作为另一个事件侦听器被注册
</script>
</body>
</html>

代码清单 10.6 事件侦听器的执行顺序
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件侦听器的执行顺序</title>
</head>
<body>
<button id="huangzihan">黄子涵</button>
<script>
var huangzihan = document.getElementById('huangzihan');
function sayHzh1() {
alert("黄子涵是帅哥!");
}
function sayHzh2() {
alert("黄子涵是靓仔!");
}
function sayHzh3() {
alert("黄子涵真厉害!");
}
function sayHzh4() {
alert("黄子涵真聪明!");
}
huangzihan.addEventListener('click', sayHzh1, false);
huangzihan.addEventListener('click', sayHzh2, false);
huangzihan.addEventListener('click', sayHzh3, false);
huangzihan.addEventListener('click', sayHzh4, false);
huangzihan.addEventListener('click', sayHzh1, false); // 根据规则,这次注册将被忽略
// 在点击按钮时,应该以“黄子涵是帅哥!”、“黄子涵是靓仔!”、“黄子涵真厉害!”、“黄子涵真聪明!”的顺序显示对话框
// 在Firefox、Google Chrome以及Safari中,将会以预想的情况执行
</script>
</body>
</html>

事件侦听器对象
通常,只需要使用函数就能够指定事件侦听器。一些浏览器还可以将含有 handleEvent() 方法的对象指定为事件侦听器(代码清单 10.7)。
代码清单 10.7 将对象注册为事件侦听器
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>将对象注册为事件侦听器</title>
</head>
<body>
<button id="huangzihan">黄子涵</button>
<script>
var huangzihan = document.getElementById('huangzihan');
var eventListener = {
hzh1: '黄子涵比彭于晏要帅!',
hzh2: '黄子涵比吴彦祖要帅!',
hzh3: '黄子涵比尤雨溪厉害!',
hzh4: '上面说的都是假话!',
handleEvent: function (e) {
alert(this.hzh1);
alert(this.hzh2);
alert(this.hzh3);
alert(this.hzh4);
}
};
huangzihan.addEventListener('click', eventListener, false);
// 在点击按键时将会显示四个消息对话框
</script>
</body>
</html>

原本在 DOM Level 2 Events 中,EventListener 接口的定义仅仅是一种含有 handleEvent() 方法的一种接口。对于Java 这类函数不是第一类的语言来说,这种定义有效的。然而,在 DOM Level 2 Events 的附录中对ECMAScript Language Binding 进行定义时,又规定了 EventListener 对象只是一个函数。
因此,JavaScript 是可以向 addEventListener() 方法传递函数的。DOM Level 3 会对 EventListener 究竟是一个函数还是一个对象进行表述,不过目前还没有定论。所以,可以认为在 JavaScript 中向 addEventListener() 方法传递对象,是一种与 DOM 的定义向背的做法。但是现在主要的浏览器都对这一功能进行了实现,所以如果非要这样使用也不会有什么问题。
还可以将事件对象作为参数传递给一个事件侦听器。
为了便于今后的说明,先在此对两个词进行定义。第一个词是事件目标。这是触发了某个事件的元素,可以通过事件对象的target属性对其引用。另一个词是侦听器目标。这是注册了某个事件侦听器的元素,可以通过事件对象的currentTarget属性对其引用。
10.2.4 事件处理程序 / 事件侦听器内的 this 引用
在事件处理程序内的 this 所引用的对象即是设定了该事件处理程序的元素。如果是像下面这样的代码
,确实是没有什么问题的。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试</title>
</head>
<body>
<button id="hzh">黄子涵</button>
<script>
document.getElementById('hzh').onclick = function () {
/* this 是 #hzh元素 */
alert("黄子涵");
};
</script>
</body>
</html>

然而,下面这种情况则会有些不同。lib 可能会被认为是 this 所引用的对象,但事实上,this 引用的是设定了事件处理程序的元素。
var Listener = function () {};
lib.handleClick = function (event) { /* this 引用的是 lib?*/ };
document.getElementById('hzh').onclick = lib.handleClick;
// => 在 lib.handleClick 中,this引用的不是 lib 而是 #hzh元素
如果希望在 lib.handleClick 内通过 this 引用 lib,可以像下面这样,先包装一个匿名函数之后设定。
document.getElementById('hzh').onclick = function (event) {
lib.handleClick(event);
// => 在 lib.handleClick 中,this引用的不是 lib 而是 #hzh元素
};
对于事件侦听器来说,上面的情况同样成立。在 JavaScript 中,我们必须对 this 的使用方式十分小心。

浙公网安备 33010602011771号