第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 的使用方式十分小心。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)