7.3 事件处理
事件处理器的基本概念
JavaScript 中的事件处理器是非常重要的,因为它们允许我们在交互式 Web 应用程序中响应用户的操作。通过添加事件处理器,我们可以使网页变得更加动态和交互式,从而提升用户体验。 在 JavaScript 中,事件处理器是一段代码,它在某个事件发生时被执行。事件处理器通常与 DOM 元素关联,当元素发生特定的事件时,它会被触发执行。 事件处理器的基本概念包括以下几点:
- 事件类型:事件类型是指发生的事件的种类,例如点击事件、鼠标移动事件、键盘按键事件等。 以下是 JavaScript 中常见的事件类型:
- 鼠标事件:包括 click(点击)、dblclick(双击)、mousedown(鼠标按下)、mouseup(鼠标松开)、mouseover(鼠标移入)、mouseout(鼠标移出)等。
- 键盘事件:包括 keydown(按下键盘)、keyup(松开键盘)等。
- 表单事件:包括 submit(提交表单)、reset(重置表单)、change(表单元素值改变)、focus(获得焦点)、blur(失去焦点)等。
- 窗口事件:包括 load(窗口加载完成)、unload(窗口关闭)、resize(窗口大小改变)、scroll(窗口滚动)等。
- 文档事件:包括 DOMContentLoaded(DOM 加载完成)、readystatechange(文档状态改变)等。
- 视频/音频事件:包括 play(播放)、pause(暂停)、ended(播放完成)等。
- 拖放事件:包括 dragstart(拖动开始)、drag(拖动中)、dragend(拖动结束)、dragenter(拖动元素进入目标元素)、dragleave(拖动元素离开目标元素)、dragover(拖动元素悬停在目标元素上)、drop(拖动元素放置在目标元素中)等。
- 触摸事件:包括 touchstart(触摸开始)、touchmove(触摸移动)、touchend(触摸结束)等。 后边会详细讲解一些常见的事件。
- 目标元素:目标元素是指触发事件的 DOM 元素,例如被点击的按钮、鼠标移动过的图片、键盘按下的输入框等。
- 事件处理器函数:事件处理器函数是在事件发生时执行的 JavaScript 函数,它通常用于响应事件并执行相应的操作。事件处理器函数可以直接作为属性赋值给目标元素,或者使用 addEventListener() 方法添加到目标元素上。 例如,以下代码演示了如何向按钮添加 click 事件处理器:
const button = document.querySelector('#myButton');
// 直接将事件处理器函数作为按钮的属性值
button.onclick = function() {
console.log('Button clicked!');
};
// 使用 addEventListener() 方法添加事件处理器
button.addEventListener('click', function() {
console.log('Button clicked!');
});
在以上示例中,当按钮被点击时,都会触发相应的事件处理器函数,输出 "Button clicked!"。
事件绑定与删除
1 直接赋值属性:通过将事件处理器函数赋值给目标元素的属性,可以直接将事件处理器绑定到元素上。例如:
const button = document.querySelector('#myButton');
button.onclick = function() {
console.log('Button clicked!');
};
对应的删除绑定操作也是给onclick属性赋值为null或者undefined
button.onclick = undefined;
或者
button.onclick = null;
除了在script标签中给dom元素的事件属性赋值之外,还可以直接在html内直接给dom元素的属性赋值Javscript代码,例如:
<button onclick="alert('Hello, world!')">Click me</button>
在这个例子中,当用户点击<button>元素时,会弹出一个警告框,显示"Hello, world!"。
如果需要在onclick属性中编写多行JavaScript代码,可以使用分号(;)将代码分隔开。例如:
<button onclick="alert('Hello,'); alert('world!');">Click me</button>
在这个例子中,当用户点击<button>元素时,会依次弹出两个警告框,分别显示"Hello,"和"world!"。
需要注意的是,虽然可以使用onclick属性直接写JavaScript代码,但这种方式会将HTML和JavaScript代码混在一起,不利于维护和复用。
2.addEventListener() 方法:使用 addEventListener() 方法可以向元素添加事件处理器。这种方法可以为同一种事件类型添加多个处理器函数,同时还可以设置事件处理器的执行顺序。例如:
const button = document.querySelector('#myButton');
const handleClick = function() {
console.log('Button clicked!');
};
button.addEventListener('click', handleClick);
使用 removeEventListener从元素中删除事件处理器,需要传递与添加事件处理器时相同的事件类型和处理器函数。例如:
const button = document.querySelector('#myButton');
const handleClick = function() {
console.log('Button clicked!');
};
button.addEventListener('click', handleClick);
// 删除事件处理器
button.removeEventListener('click', handleClick);
关于如何控制执行顺序在事件的执行顺序章节会详细讲解。
直接赋值事件属性和addEventListener 都可以用于将事件处理器绑定到一个 DOM元素上,但是它们之间有一些区别:
- 处理多个事件:直接赋值事件属性 只能为一个事件类型(如 click 事件)添加一个事件处理器,而 addEventListener 可以为同一种事件类型添加多个事件处理器,这样就可以在同一个元素上执行多个操作。
- 兼容性:直接赋值事件属性 是早期版本的 JavaScript 的写法,不过现在它已经得到了广泛的兼容,但在某些情况下可能会存在兼容性问题。addEventListener 是 DOM Level 2 规范中的写法,是一种更加标准化的方法,现在已经被广泛支持。
- 事件监听的阶段:直接赋值事件属性 只能将事件处理器添加到事件冒泡阶段,而addEventListener 可以在事件捕获阶段或事件冒泡阶段添加事件处理器,通过第三个参数可以选择不同的监听阶段,这样可以更加精确地控制事件的处理。 综上所述,虽然直接赋值事件属性 和addEventListener 都可以用于绑定事件处理器,但是addEventListener 是一种更加灵活、兼容性更好、可扩展性更强的方法。如果需要添加多个事件处理器或者需要更加精确地控制事件的处理,建议使用addEventListener。
事件捕获与事件冒泡
DOM树是有层级嵌套关系的,根元素是document 所以当里边的元素被触发事件时 实际上也会经过父元素,所以就有了 事件冒泡和事件捕获的过程。 事件冒泡和事件捕获是 DOM 事件传播的两个阶段,事件首先在捕获阶段从外到内依次经过祖先元素、父元素,最后到达目标元素,然后在冒泡阶段从内到外依次经过目标元素、父元素、祖先元素,直到文档根节点为止。默认情况下,事件处理程序是在冒泡阶段被触发执行的。
改变绑定事件的执行顺序
在事件绑定时,可以使用 addEventListener() 方法来指定事件的传播阶段,第三个参数可以指定 useCapture 参数,若该参数为 true,则事件处理程序在捕获阶段被执行,否则在冒泡阶段被执行。例如:
// 在捕获阶段执行事件处理程序
element.addEventListener("click", myFunction, true);
// 在冒泡阶段执行事件处理程序(默认情况下)
element.addEventListener("click", myFunction, false);
以下是一个简单的事件捕获和事件冒泡的例子,用于说明它们的执行顺序:
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<div id="outer">
<div id="inner">Click me!</div>
</div>
<script>
const outer = document.getElementById("outer");
const inner = document.getElementById("inner");
outer.onclick = function() {
console.log("outer clicked");
};
outer.addEventListener("click", function() {
console.log("outer clicked useCapture true");
}, true);
inner.addEventListener("click", function() {
console.log("inner clicked useCapture true");
}, true);
outer.addEventListener("click", function() {
console.log("outer clicked useCapture false");
});
inner.addEventListener("click", function() {
console.log("inner clicked useCapture false");
});
inner.onclick = function() {
console.log("inner clicked");
};
</script>
</body>
</html>
执行结果如下:
通过结果可以看到,先进行事件捕获的过程 从外到内的触发事件,然后进行时间冒泡的过程,从内到外的触发, 通过属性onclick绑定的事件是绑定在冒泡阶段。onclick和addEventListener绑定的事件触发顺序和绑定的顺序一致。
事件委托
事件委托是JavaScript中的一种常见的设计模式,它可以使代码更加高效和可维护。下面是一些事件委托的例子:
- 列表中的点击事件 假设你有一个动态生成的列表,你想要为每个列表项添加一个点击事件。你可以使用事件委托来减少事件处理程序的数量,从而提高性能。具体来说,你可以将事件处理程序添加到列表的父元素上,然后在父元素中捕获事件并确定被点击的子元素。
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
const myList = document.querySelector('#myList');
myList.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('You clicked on', event.target.innerText);
}
});
</script>
</body>
</html>
在上面的代码中,我们将点击事件处理程序添加到列表的父元素上,然后在处理程序中检查被点击的元素是否是列表项(即
事件对象及其属性
在上面的事件委托的例子中,使用到了event对象,它是事件处理函数的参数,里边包含了事件的全部信息,我们可以根据这个事件对象获取事件的信息。
outer.addEventListener("click", function(event) {
console.log(event)
});
下面是打印出来的click事件对象的部分截图:
列举一些使用频率较高的属性
- event.target:事件的目标元素。这是事件最初发生的地方。
- event.currentTarget: 事件处理程序绑定的元素。
- event.type:事件的类型,如“click”、“mouseover”等。
- event.preventDefault():阻止事件默认行为的方法。例如,在点击链接时,防止页面跳转。
- event.stopPropagation():停止事件冒泡的方法。防止事件传递到父元素。
- event.keyCode:按下的键的键码值。
- event.pageX / event.pageY:事件发生时鼠标指针的X和Y坐标。
- event.target.value:与表单元素关联的值,例如input或textarea的内容。
- event.timeStamp:事件发生的时间戳。
event.type
event.type 是一个字符串,它代表当前事件的类型。在 JavaScript 中,有很多种类型的事件,比如 click、mouseover、keydown 等等。通过 event.type 属性可以获取当前事件的类型,以便进行相应的处理。 举个例子,如果我们希望在点击一个按钮时执行某个操作,可以这样写:
const button = document.querySelector('#myButton');
button.addEventListener('click', (event) => {
console.log('Button clicked!');
console.log('Event type: ', event.type);
// do something...
});
当用户点击按钮时,会触发一个 click 事件,事件处理程序会输出 Button clicked!,并打印出当前事件的类型。如果用户按下键盘上的某个键,也会触发一个相应的事件,此时 event.type 的值就是对应的键盘事件类型,比如 keydown、keyup 等。
event.stopPropagation
event.stopPropagation它用于停止事件在 DOM 树上进一步传播,阻止事件冒泡。在 JavaScript 中,如果一个事件在某个元素上触发了,它会一直沿着 DOM 树向上传播,直到达到文档根节点。这个过程称为事件冒泡(Bubbling)。如果在 DOM 树上的某个父元素上绑定了同类型的事件处理程序,那么这个处理程序也会被触发。如果我们希望在某个子元素上处理事件时,不让事件继续向上传播,可以使用 event.stopPropagation() 方法来阻止事件冒泡。 举个例子,假设我们有如下 HTML 代码:
<div id="outer">
<div id="inner">Click me</div>
</div>
我们在 #outer 元素上绑定一个点击事件处理程序:
const outer = document.querySelector('#outer');
outer.addEventListener('click', (event) => {
console.log('Outer clicked!');
});
在 #inner 元素上绑定一个点击事件处理程序,并在其中调用 event.stopPropagation():
const inner = document.querySelector('#inner');
inner.addEventListener('click', (event) => {
console.log('Inner clicked!');
event.stopPropagation();
});
当用户点击 #inner 元素时,事件会被捕获并传递到 #inner 元素,然后触发 #inner 元素上的点击事件处理程序。在该处理程序中,我们调用了 event.stopPropagation(),阻止事件继续向上传播,因此 #outer 元素上的点击事件处理程序不会被触发。如果我们不调用 event.stopPropagation(),那么事件会继续向上传播,最终触发 #outer 元素上的点击事件处理程序,控制台会输出 Outer clicked!。
event.preventDefault
event.preventDefault() 是一个事件方法,用于取消事件的默认行为。在 HTML 中,每种事件都有默认的行为。比如,在一个链接上点击时,浏览器会跳转到链接的 URL;在一个表单中提交时,浏览器会刷新页面并将表单数据提交到服务器;在一个滚动条上滚动时,浏览器会滚动页面等等。如果我们希望在事件发生时取消这些默认行为,可以使用 event.preventDefault()。 下面列举一些使用event.preventDefault()阻止事件默认行为的方法:
- 阻止链接的默认跳转行为 在用户点击一个链接时,页面会自动跳转到链接的目标地址。如果希望在点击链接时,不进行页面跳转,可以在链接的click事件中使用event.preventDefault()方法,例如:
<a href="https://example.com" id="myLink">Click me</a>
<script>
document.getElementById("myLink").addEventListener("click", function(event) {
event.preventDefault();
});
</script>
在这个例子中,使用document.getElementById()方法获取了<a>元素的引用,并调用addEventListener()方法来注册一个点击事件监听器。该监听器会在用户点击<a>元素时被调用,执行event.preventDefault()方法,从而阻止了链接的默认跳转行为。
需要注意的是,如果需要跳转到链接的目标地址,可以使用JavaScript代码手动进行页面跳转,例如:
<a href="https://example.com" id="myLink">Click me</a>
<script>
document.getElementById("myLink").addEventListener("click", function(event) {
event.preventDefault();
window.location.href = this.href;
});
</script>
在这个例子中,当用户点击链接时,会执行event.preventDefault()方法阻止链接的默认跳转行为,然后使用window.location.href属性手动进行页面跳转。 2. 阻止表单的默认提交行为,可以在表单的submit事件中使用 event.preventDefault()方法,避免页面刷新,以便使用AJAX技术实现异步提交表单数据。
document.querySelector('form').addEventListener('submit', function(event) {
event.preventDefault();
// 使用AJAX提交表单数据
});
- 阻止鼠标右键菜单的默认弹出行为,可以在鼠标右键按下事件中使用
event.preventDefault()方法,避免显示默认的上下文菜单。
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
// 自定义右键菜单
});
- 阻止滚动条的默认滚动行为,可以在滚动事件中使用event.preventDefault()方法,避免滚动条滚动。
document.addEventListener('scroll', function(event) {
event.preventDefault();
// 禁止页面滚动
});
需要注意的是,event.preventDefault()方法只能阻止事件的默认行为,并不能阻止事件传播。如果需要阻止事件传播,需要使用event.stopPropagation()方法。同时,不是所有的事件都有默认行为,对于没有默认行为的事件,使用event.preventDefault()方法没有任何效果。
event.currentTarget和event.target的区别
- event.target:指的是最初触发事件的 DOM 元素,即事件的源头。如果事件是冒泡(Bubbling)事件,那么它会从事件源头开始,逐级向上传播,直到达到文档根节点。在冒泡过程中,event.target 的值不会改变,它永远是最初触发事件的元素。
- event.currentTarget:指的是事件处理程序当前正在处理事件的那个元素。如果事件是冒泡事件,那么 event.currentTarget 的值会随着事件的冒泡过程而改变,它始终指向当前正在处理事件的元素。 举个例子,假设有如下 HTML 代码:
<div id="outer">
<div id="inner">Click me</div>
</div>
当用户点击 #inner 元素时,会触发一个点击事件。如果这个事件是冒泡事件,那么事件会从 #inner 元素开始冒泡,直到 #outer 元素和文档根节点。
- 如果事件处理程序绑定在 #inner 元素上,那么 event.target 的值为 #inner,event.currentTarget 的值也为 #inner。
- 如果事件处理程序绑定在 #outer 元素上,那么 event.target 的值为 #inner,event.currentTarget 的值为 #outer。
- 如果事件处理程序绑定在文档根节点上,那么 event.target 的值为 #inner,event.currentTarget 的值为文档根节点。 因此,event.target 和 event.currentTarget 的值取决于事件处理程序的绑定位置和事件的冒泡过程。