JavaScript中的Event事件对象详解
一、事件对象(Event)概述
1. 事件对象的定义
event 对象是浏览器自动生成的对象,当用户与页面进行交互时(如点击、键盘输入、鼠标移动等),事件触发时就会自动传递给事件处理函数。event 对象包含了与事件相关的各种信息,开发者可以通过访问 event 对象的属性来获取具体的事件细节,并执行相应的操作。
2. 事件的类型
在 JavaScript 中,事件类型非常丰富,常见的包括:
- 鼠标事件:click(点击)、dblclick(双击)、mousedown(按下鼠标)、mouseup(释放鼠标)、mousemove(移动鼠标)、mouseenter(鼠标进入元素)、mouseleave(鼠标离开元素)等。
- 键盘事件:keydown(按下键盘)、keypress(按下字符键)、keyup(释放键盘)。
- 表单事件:submit(提交表单)、focus(聚焦)、blur(失焦)。
- 触摸事件:touchstart(触摸开始)、touchmove(触摸移动)、touchend(触摸结束)。
- 窗口事件:resize(窗口大小变化)、scroll(滚动)。
通过绑定不同类型的事件,开发者可以实现丰富的交互效果。
注:其实事件一直都是存在的(不管有没有绑定 或 监听),它只是没有事件处理程序而已!!!
JavaScript事件是在浏览器文档(document)窗口中的发生的特定的交互瞬间;而JavaScript和HTML之间的交互行为就是通过事件来触发的。
事件处理程序:
事件处理程序:我们用户在页面中进行的点击这个动作,鼠标移动的动作,网页页面加载完成的动作等,都可以称之为事件名称,
即:click、mousemove、load等都是事件的名称。响应某个事件的函数则称为事件处理程序,或者叫做事件侦听器。
二、Event 对象的常见属性
1. type
属性
type
属性返回触发事件的类型,以字符串形式表示。例如,鼠标点击触发的事件类型为 "click"
,键盘按下触发的事件类型为 "keydown"
。这是判断事件类型的重要依据。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件类型打印</title>
</head><body><button id="clickBtn">点击按钮</button><input type="text" id="inputBox" placeholder="输入内容"><script>const clickBtn = document.getElementById('clickBtn');const inputBox = document.getElementById('inputBox');clickBtn.addEventListener('click', (event) => {console.log(`你触发了 ${event.type} 事件`);});inputBox.addEventListener('input', (event) => {console.log(`你触发了 ${event.type} 事件,当前输入内容为:${inputBox.value}`);});document.addEventListener('keydown', (event) => {console.log(`检测到 ${event.type} 事件,你按下了 ${event.key} 键`);});</script>
</body></html>
2.target:找到事件真正的源头
作用:指向用户实际操作的那个元素(哪怕事件是从父元素冒泡上来的)
案例:点击列表项显示被点击的内容
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件类型打印</title>
</head><body><ul id="list"><li>苹果</li><li>香蕉</li><li>橙子</li></ul><script>list.addEventListener('click', (event) => {// 无论点击哪个<li>,都会显示对应的水果名称console.log(`你点击了:${event.target.textContent}`);});document.addEventListener('click', (event) => {console.log(event.target); // 输出被点击的元素});</script>
</body></html>
3.currentTarget 属性
currentTarget
与 target
不同,它表示当前事件监听器所绑定的元素。即使事件是从子元素触发的,currentTarget
始终指向绑定事件处理函数的元素。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>currentTarget与target对比</title><style>.parent {border: 2px solid blue;padding: 10px;}.child {border: 2px solid green;padding: 10px;}.grand-child {border: 2px solid red;padding: 10px;}</style>
</head><body><div class="parent" id="parent">父元素<div class="child" id="child">子元素<button class="grand-child" id="btn">孙元素按钮</button></div></div><script>const parent = document.getElementById('parent');parent.addEventListener('click', (event) => {console.log("currentTarget:", event.currentTarget);console.log("target:", event.target);});</script>
</body></html>
4.key:获取键盘按下的字符(推荐使用)
作用:返回按下的具体按键(字母、数字、功能键等),替代已废弃的keyCode
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>key 属性示例</title>
</head><body><input type="text" id="inputField"><script>const inputField = document.getElementById('inputField');inputField.addEventListener('keydown', (event) => {console.log(`你按下了:${event.key}`);});</script>
</body></html>
5.altKey/ctrlKey/shiftKey:检测修饰键是否按下
作用:判断是否同时按下了 Alt/Ctrl/Shift 键(返回 true/false)
案例:检测 Ctrl+S 组合键,在控制台打印提示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>修饰键检测</title>
</head><body><script>document.addEventListener('keydown', (event) => {if (event.ctrlKey && event.key === 's') {event.preventDefault(); // 阻止默认保存行为console.log('Ctrl + S 组合键被按下');}});</script>
</body></html>
6.button:判断按下的鼠标按钮
作用:在鼠标点击事件中,区分左 / 中 / 右键(0 = 左键,1 = 中键,2 = 右键)
案例:点击鼠标,在控制台打印按下的按钮信息
三、事件对象的核心方法
1.阻止默认行为:preventDefault()
用于取消事件的默认动作,如阻止表单提交或链接跳转:
document.querySelector('a').addEventListener('click', (event) => {event.preventDefault(); // 阻止链接跳转console.log('链接被点击,但未跳转');
});
2 阻止事件冒泡:stopPropagation()
防止事件在DOM层级中继续向上传播:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>stopPropagation示例</title><style>#container {width: 200px;height: 200px;background-color: lightblue;padding: 10px;}#child {width: 100px;height: 100px;background-color: lightcoral;}</style>
</head><body><div id="container">父容器<div id="child">子元素</div></div><script>const container = document.getElementById('container');const child = document.getElementById('child');container.addEventListener('click', (event) => {console.log('容器被点击');});child.addEventListener('click', (event) => {event.stopPropagation(); // 阻止事件冒泡到父容器console.log('子元素被点击,但不会触发父元素事件');});</script>
</body></html>
⑴把 event.stopPropagation();注释掉
3.立即停止事件流:stopImmediatePropagation()
同时阻止冒泡和同一元素上其他监听器的执行:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>stopImmediatePropagation示例</title><style>button {padding: 10px 20px;font-size: 16px;}</style>
</head><body><button id="button">点击按钮</button><script>document.getElementById('button').addEventListener('click', (event) => {console.log('第一个监听器');event.stopImmediatePropagation();});document.getElementById('button').addEventListener('click', (event) => {console.log('第二个监听器(不会执行)');});</script>
</body></html>
四、事件流:
浏览器层次顺序:document -> html -> body -> div父元素 -> input子元素】,document最上层祖先元素, input最下层后代元素。
1.🍀什么是事件流:
事件流是描述从页面中接收事件的顺序【从内到外(冒泡),从外到内(捕获)】;
IE与原来的NetScape(网景),对于事件流提出的是完全不同的顺序。IE团队提出的是事件冒泡流;NetScape的事件流是事件捕获流。
简单的讲:当给一个DIV绑定一个点击事件,又在DIV里面放一个按扭并给按扭也绑定一个点击事件,此时你点击里面按扭的同时,外面DIV的点击事件也会被触发。
2.🍀事件冒泡:
所谓事件冒泡 就是事件最开始从最具体的元素(文档中嵌套层次最深的那个点【当前绑定事件的那个元素】)接收,然后逐级向上传播至最不具体的那个节点(document节点,即最上层的节点)。
阻止事件冒泡方法:event.stopPropagation()
阻止默认行为方法:event.preventDefault()
注意:这两个方法属于event对象中的。
3.🍀事件捕获:
事件捕获和事件冒泡刚好【相反】,它是事件从最不具体的节点(document)先接收到事件,然后再向下逐一捕获至(文档中嵌套层次最深的那个点【当前绑定事件的那个元素】)。
简单地说,事件冒泡和事件捕获都是一种事件传递的机制。这种机制可以使事件在不同级
的元素间传递。事件冒泡是从事件触发的源节点,向父节点传递,直到到达最顶节点。而事件
捕获则是从最顶节点,逐步向下传递,直到到达事件触发的源节点。
在一些标准的浏览器中,如IE9以上,Chrome 、Firefox 、Safari浏览器等,支持这两种冒泡方式。而事实上,
准确的说,是这两种方式的混合方式。因为W3C采取的就是这两种方式的结合:先从顶级节点
开始,将事件向下传递至源节点,再从源节点冒泡至顶节点。
而在IE及Opera(以下说的都是老版本的欧朋,新版本的欧朋经检测是支持事件捕获的)
下,是不支持事件捕获的。而这个特点最明显体现在事件绑定函数上。IE、Opera的事件
绑定函数是attachEvent,而Chrome等浏览器则是addEventListener,
而后者比前者的参数多了一个——这个参数是一个布尔值,这个布尔值由用户决定,用户若设为true,则该绑定事件以事件捕获的形式参与,若为false则以事件冒泡的形势参与。
而这个参数在IE和Opera浏览器中是不存在的——根本原因是它们不支持事件捕获。
五、📚事件绑定:
1.🍀事件绑定方式1:
HTML事件内嵌绑定,就是将事件直接内嵌内HTML结构标签元素内的【不推荐用,因为不灵活】
<input type="button" onclick="alert("我是事件绑定方式一:HTML事件处理程序,我是内嵌在HTML结构中的");" value="事件绑定方式一【内嵌】" />
<input type="button" onclick="mupiaoFn()" value="事件绑定方式一【调用】" />
HTML事件处理程序:
function mupiaoFn() {alert("我是事件绑定方式1:HTML事件处理程序");
};
2.🍀事件绑定方式2:
【DOM 0级事件处理程序】就是把一个函数/方法赋给一个事件处理程的 属性如:id 、class 、元素名等 【用得最多,兼容性好,简单,灵活,跨浏览器 ;缺点:不能绑定多个同类型事件】
<input type="button" name="eventBtn2" id="eventBtn2" value="事件绑定方式二【通用属性绑定】" />
DOM 0级事件处理程序
var Btn2 = document.getElementById("eventBtn2");//给谁绑定事件,就要先获取谁
绑定事件1:【赋给方式】
Btn2.onclick = function() {alert("我是事件绑定方式二:DOM 0级事件处理程序");
};
绑定事件2:【调用方式】
function publick() {alert("我也是事件绑定方式二:DOM 0级事件处理程序");
};Btn2.onclick = publick; //注:publick后面不要加()括号,否则会变为立即执行函数!
删除事件:
Btn2.onclick = null;
注意事项:
- 在DOM0级事件处理程序推出之后,被广为使用,可是出现了这样一个问题,当我们希望给同一个元素/标签绑定多个同类型事件的时候(如,为上面的按扭标签绑定2个或是个以上的点击事件),是不被允许的。
- 那么,此时,出现了另一种事件处理程序,就是DOM2级的事件处理程序,【注:没有DOM1级事件这个概念哦】在DOM2级当中,定义了两个基本方法,
- 用于处理指定(即绑定)和删除事件处理程序的操作,分别是addEventListener()和removeEventListener(),IE9+、FireFox、Safari、Chrome和Opera都是支持DOM2级事件处理程序的。
- 对于IE8-,则使用的是IE专有的事件处理程序:两个类似的方法——attachEvent()与detachEvent()。
3.🍀事件绑定方式3:
【DOM 2级事件处理程序 事件委托】:
除了以上两种事件绑定方式都需要一个一个的绑定和添加事件,还有一种(事件委托)把事件处理程序挂载在父节点上,利用事件冒泡机制在父节点统一处理事件,这样就不用一个一个的绑定和添加事件了。
addEventListener()和removeEventListener()监听事件接收3个参数:
- 事件类型(注:不要加 on)
- 事件函数(事件处理程序)
- 是否捕获 false:冒泡,true:捕获
注:在IE浏览器中用 attachEvent()和detachEvent()监听事件接收2个参数:事件类型(注:要加 on), 处理函数/只支持冒泡
例:
<ul id="list"><li class="item">Item 1</li><li class="item">Item 2</li><li class="item">Item 3</li>...
</ul>
DOM 2级事件处理程序 / 监听事件
var list= document.getElementById("list");//给谁绑定事件,就要先获取谁
添加监听事件1:【内嵌方式】
list.addEventListener('click' , function(event) {alert("我是事件绑定方式二:DOM 2级事件处理程序");// const li = event.target;const li = event.target.closest('li'); // closest()方法返回最近的祖先元素if (item.style.backgroundColor) {li.style.backgroundColor = 'red'} else {li.style.backgroundColor = 'blue';}} , false); //false:冒泡,true:捕获
添加监听事件2:【调用方式】
list.addEventListener('click' , addevFn , false);function addevFn(event) {alert("我是事件绑定方式二:DOM 2级事件处理程序 【调用方式】");// const li = event.target;const li = event.target.closest('li');if (item.style.backgroundColor) {li.style.backgroundColor = 'red'} else {li.style.backgroundColor = 'blue';}
};
IE8及以下的添加和删除监听事件方法:(注:IE9及以上的就用上面的方法啦)
IE8及以下的添加监听事件1:【内嵌方式】 (注 attachEvent 和 detachEvent方法只传两个参数,前面两个和上面一样,而第3个参数是因为在IE中默认就是冒泡方式,所以不用传第3个参数啦)
list.attachEvent("onclick" , function(event) {alert("我是IE8及以下的添加监听事件方法,【内嵌方式】");
});
IE8及以下的添加监听事件2:【调用方式】(注:attachEvent 和 detachEvent 事件类型前而一定要加 on 才可以哦)
list.attachEvent("onclick" , addevFn3);function addevFn3(event) {alert("我是IE8及以下的添加监听事件方法,【调用方式】");
};
IE8及以下的删除监听事件
Btn3.detachEvent("onclick" , addevFn3);
🍀事件方面性能优化:
在JavaScript代码当中,添加到页面中的事件越多,页面的性能也就越差。导致这一问题的原因主要有:
- 每个函数都是对象,都会占用内存。内存中对象越多,性能也就越差。
- 必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
- 为了进行页面的性能优化,因此我们会采用两种方法,就是上面提到的——事件委托和事件处理程序的移除。
事件委托的好处:
- 在大量子元素需要绑定事件处理程序的情况下,事件委托可明显减少内存消耗
- 事件委托实现了动态绑定,后续新增的子节点不需要额外去绑定事件处理程序
事件委托的缺点:
- 事件委托必需要能支持冒泡的事件类型,有些事件是不支持冒泡的 如(1、focus事件;2、blur事件;3、scroll事件;4、mouseenter和mouseleave事;5、mouseover和mouseout事件;6、mousemove事件;7、keypress事件;8、beforeunload事件;9、domcontentloaded事件;10、cut、copy和paste事件等)
- 需要确保事件对象(event.target)是对应要处理的元素的!
📚事件委托:
关于什么时候使用事件委托,其实,简单来说,当时一个页面事件处理程序比较多的时候,我们通常情况下会使用它。
事件委托主要利用了事件冒泡,只指定一个事件处理程序,就可以管理一个类型的所有事件。例如:我们为整个一个页面制定一个onclick事件处理程序,
此时我们不必为页面中每个可点击的元素单独设置事件处理程序
事件委托:给元素的父级或者祖级,甚至页面绑定事件,然后利用事件冒泡的基本原理,通过事件目标对象进行检测,然后执行相关操作。其优势在于:
大大减少了事件处理程序的数量,在页面中设置事件处理程序的时间就更少了(DOM引用减少——也就是上面我们通过id去获取标签,所需要的查找操作以及DOM引用也就更少了)。
document(注:上面的例子没有绑定在document上,而是绑定到了父级的div上,最为推荐的是绑定在document上)对象可以很快的访问到,而且可以在页面生命周期的任何时点上为它添加事件处理程序,并不需要等待DOMContentLoaded或者load事件。换句话说,只要可单击的元素在页面中呈现出来了,那么它就立刻具备了相应的功能。
整个页面占用的内存空间会更少,从而提升了整体的性能。
📚移除事件处理程序
每当将一个事件处理程序指定给一个元素时,在运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。连接数量也直接影响着页面的执行速度。
所以,当内存中存在着过时的“空事件处理程序”的时候,就会造成Web应用程序的内存和性能问题。
那么什么时候会造成“空事件处理程序”的出现呢?
文档中元素存在事件,通过一些DOM节点操作(removeChild、replaceChild等方法),移除了这个元素,但是DOM节点的事件没有被移除。
innerHTML去替换页面中的某一部分,页面中原来的部分存在事件,没有移除。
页面卸载引起的事件处理程序在内存中的滞留。
🍀解决方法:
- 合理利用事件委托;
- 在执行相关操作的时候,先移除掉事件,再移除DOM节点;
- 在页面卸载之前,先通过onunload事件移除掉所有事件处理程序。