在JavaScript中经常可以看到事件对象的使用,所以打算写一篇博客来记录下学习此知识点的过程。
什么是事件对象?
首先我们需要了解一下事件对象是什么,先来看下MDN给出的定义
Event 接口表示在 DOM 中出现的事件。
一些事件是由用户触发的,例如鼠标或键盘事件;而其他事件常由 API 生成,例如指示动画已经完成运行的事件,视频已被暂停等等。事件也可以通过脚本代码触发,例如对元素调用 HTMLElement.click() 方法,或者定义一些自定义事件,再使用 EventTarget.dispatchEvent() 方法将自定义事件派发往指定的目标(target)。—— MDN-Event
可以看到事件对象是一个对象,而这个对象记录了事件触发的时候的相关信息,比如:
触发鼠标点击事件的时候,事件对象记录了鼠标的位置相关的信息
触发键盘事件的时候,事件对象会记录下用户按下的对应的按键的相关信息
如何使用(获取)事件对象?
通过DOM LV2的方式添加绑定事件的时候,回调函数的第一个参数就是事件对象,一般可以将其命名为e
、event
1
2
3
document . querySelector ( '#test' , function ( e ){
console . log ( e );
});
事件流
事件流 描述了页面接收事件的顺序。——《JavaScript高级程序设计》
假如页面上有一个元素,该元素触发事件的时候,首先水发生事件捕获 ,然后才是事件冒泡 。
事件冒泡
当一个元素触发事件后,会依次向上调用所有父级元素的同名事件
事件冒泡是默认存在的
验证:
比如现在页面上有这几个元素,对应的CSS以及HTML如下
CSS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# test1 {
width : 600 px ;
height : 600 px ;
background-color : red ;
}
# test2 {
width : 400 px ;
height : 400 px ;
background-color : yellow ;
}
# test3 {
width : 200 px ;
height : 200 px ;
background-color : blue ;
}
HTML:
1
2
3
4
5
< div id = "test1" >
< div id = "test2" >
< div id = "test3" ></ div >
</ div >
</ div >
现在给分别给这几个元素添加点击事件
JS:
1
2
3
4
5
6
7
8
9
10
11
12
let test1 = document . querySelector ( '#test1' );
let test2 = document . querySelector ( '#test2' );
let test3 = document . querySelector ( '#test3' );
test1 . addEventListener ( 'click' , function (){
console . log ( 'test1' );
});
test2 . addEventListener ( 'click' , function (){
console . log ( 'test2' );
});
test3 . addEventListener ( 'click' , function (){
console . log ( 'test3' );
});
此时点击蓝色的部分(div#test3
),就会依次输出test3
,test2
,test1
的消息。
如果点击黄色的部分(div#test2
),就会依次输出test2
,test1
的消息。
如果点击红色的部分(div#test1
),就会只输出test1
的消息。
可以看到,结果符合事件冒泡的顺序div#test3
->div#test2
->div#test1
(从里到外)。
事件捕获
从DOM的根元素开始去执行对应的事件 (从外到里)
注:
实际上,所有浏览器都是从 window
对象开始捕获事件,而 DOM2 Events 规范规定的是从 document
开始
由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常建议使用事件冒泡,特殊情
况下可以使用事件捕获。
相关内容详细请见——《JavaScript高级程序设计》
事件捕获需要写对应的代码才能开启,如何开启事件捕获如下:
1
document . querySelector ( '#test1' ). addEventListener ( eventType , callBack , useCapture )
说明:
参数3useCapture
接受一个布尔值,代表是否开启事件捕获
参数3为true
的时候代表开启事件捕获(默认状态为false
)
❕注意 :默认DOM 0级,也就是element.onclick = function(){}
的写法,只有冒泡没有捕获。
验证:
针对刚才冒泡的js代码做如下修改,开启事件捕获
1
2
3
4
5
6
7
8
9
test1 . addEventListener ( 'click' , function (){
console . log ( 'test1' );
}, true );
test2 . addEventListener ( 'click' , function (){
console . log ( 'test2' );
}, true );
test3 . addEventListener ( 'click' , function (){
console . log ( 'test3' );
}, true );
此时点击蓝色的部分(div#test3
),就会依次输出test1
,test2
,test3
的消息。
如果点击黄色的部分(div#test2
),就会依次输出test1
,test2
的消息。
如果点击红色的部分(div#test1
),就会只输出test1
的消息。
可以看到,结果符合事件捕获的顺序div#test1
->div#test2
->div#test3
(从外到里)。
阻止停止冒泡,捕获
如果只想把对应的事件限制在子元素内,不想触发父级的事件,那么旧需要阻止事件流动。
通过e.stopPropagation()
方法,就可以阻止事件冒泡
❕注:不推荐使用event.cancelBubble
,该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。—— MDN-Event.cancelBubble
比如对于刚才的案例,如果指向要点击事件的触发只限制在子元素内,比如只想要div#test3
点击的时候,只输出test3
,那就可以加上这个方法。
对于事件捕获(不常用):
1
2
3
4
5
6
7
8
9
10
11
test1 . addEventListener ( 'click' , function ( e ){
console . log ( 'test1' );
e . stopPropagation ();
}, true );
test2 . addEventListener ( 'click' , function ( e ){
// console.log('test2');
}, true );
test3 . addEventListener ( 'click' , function ( e ){
console . log ( 'test3' );
// e.stopPropagation();
}, true );
加上这个方法后不管点击哪里,都只会输出test1
了。
对于事件冒泡:
1
2
3
4
5
6
7
8
9
10
test1 . addEventListener ( 'click' , function ( e ){
console . log ( 'test1' );
});
test2 . addEventListener ( 'click' , function ( e ){
console . log ( 'test2' );
});
test3 . addEventListener ( 'click' , function ( e ){
console . log ( 'test3' );
e . stopPropagation ();
});
现在点击div#test3
就只会输出test3
了。
阻止事件默认行为
如果不想要a标签点击直接跳转,表单点击不直接提交可以使用下面的方法
语法:
e.preventDefault()
验证:
HTML:
1
2
3
4
< form action = "https://cn.bing.com/" method = "post" >
< button > click</ button >
</ form >
< a href = "https://cn.bing.com/" script = "preventDefault()" > link</ a >
JS:
1
2
3
4
5
6
7
8
9
10
let btn = document . querySelector ( 'button' );
let a = document . querySelector ( 'a' );
btn . addEventListener ( 'click' , function ( e ){
// 阻止表单提交
e . preventDefault ();
});
a . addEventListener ( 'click' , function ( e ){
// 阻止链接点击默认跳转
e . preventDefault ();
})
事件代理(委托)
可以利用事件冒泡的特性,可以只使用一个事件处理程序来管理一种类型的事件。——《JavaScript高级程序设计》
给所有元素共同的祖先节点添加一个事件处理程序 (可以提高性能)
使用:通过事件对象的target属性(e.target
),可以得到事件真正的触发者。
使用场景:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
< style >
ul . active {
color : red ;
border : 1 px solid rgb ( 255 , 28 , 28 );
}
</ style >
< div class = "header" >
< ul >
< li > 第1个</ li >
< li > 第2个</ li >
< li > 第3个</ li >
< li > 第4个</ li >
< li > 第5个</ li >
< li > 第6个</ li >
</ ul >
</ div >
比如页面上有这样一个tag栏,想要点击的时候对应的tag栏变色的效果,这个时候正常情况下可以使用,for循环的形式依次为每个li
元素添加点击事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取所有li元素组成的NodeList
let lis = document . querySelectorAll ( 'li' );
// 给每个NodeList添加点击事件
lis . forEach (( item ) => {
item . addEventListener ( 'click' , function (){
// 去除页面上已经激活的li样式
if ( document . querySelector ( '.active' )) {
document . querySelector ( '.active' ). classList . remove ( 'active' );
}
// 激活当前li的样式
this . classList . add ( 'active' );
});
});
同样的使用事件代理也可以解决问题,并且可以提高网页性能,不需要在使用循环的形式来给每个元素做绑定事件了,对应的JS代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
// 事件代理
let ul = document . querySelector ( 'ul' );
ul . addEventListener ( 'click' , function ( e ) {
// 如果点击的目标元素是li标签则进行样式修改
if ( e . target . tagName == 'LI' ) {
// 去除页面上已经激活的li样式
if ( document . querySelector ( '.active' )) {
document . querySelector ( '.active' ). classList . remove ( 'active' );
}
// 激活当前li的样式
e . target . classList . add ( 'active' );
}
});
🤖了解 :e.currentTarget
返回事件的监听者(事件绑定的对象)。
事件对象中的常用属性
获取事件事件触发类型
获取当前坐标的属性(点击事件)
offsetX/offsetY
:返回的是点击时候相对点击的元素左上角的相对位置(包含外边距)的坐标。
pageX/pageY
:返回的是整个文档的坐标,如果此时页面比较长可以获取文档中鼠标的坐标。
clientX/clientY
:返回的是鼠标点击的时候光标浏览器可视范围相对左上角的坐标。
screenX/screenY
:返回的是鼠标点击的时候光标相对屏幕左上角的坐标。
layerX/layerY
:往上找有定位属性的父元素的左上角(自身有定位属性的话就是相对于自身),都没有的话,就是相对于body的左上角的坐标。
PS:有关于这块的内容可以参考笔者写的另一篇文章——> 链接在这里
获取按键的属性(键盘事件)
key
:获取用户按下的物理按键的值(只读)
keyCode
(不建议使用):返回按键对应的编号。(注:该功能已从Web标准中删除,尽管一些浏览器可能仍然支持它,但它正在被丢弃。)
target属性与currentTarget属性
target
:返回事件真正的触发者
currentTarget
:返回事件的监听者(事件绑定的对象)
eventPhase属性
eventPhase
:表示事件流当前处于哪一个阶段。
返回值
含义
0
此时没有事件在处理
1
事件处于捕获阶段
2
事件已经到达触发者
3
事件处于冒泡阶段
测试:
1、事件冒泡
HTML:
CSS:
1
2
3
4
5
# test3 {
width : 200 px ;
height : 200 px ;
background-color : blue ;
}
JS:
1
2
3
4
5
6
7
let test3 = document . querySelector ( '#test3' );
test3 . addEventListener ( 'click' , function ( e ){
console . log ( 'div e.eventPhase:' + e . eventPhase );
});
document . addEventListener ( 'click' , function ( e ){
console . log ( 'doc eventPhase:' + e . eventPhase );
});
点击div#test3
依次输出
div e.eventPhase:2
doc eventPhase:3
2、事件捕获
对上面的代码做一点修改
1
2
3
4
5
6
7
let test3 = document . querySelector ( '#test3' );
test3 . addEventListener ( 'click' , function ( e ){
console . log ( 'div e.eventPhase:' + e . eventPhase );
});
document . addEventListener ( 'click' , function ( e ){
console . log ( 'doc eventPhase:' + e . eventPhase );
}, true );
点击div#test3
依次输出
doc eventPhase: 1
div e.eventPhase: 2
e.button
:MouseEvent.button
是只读属性,它返回一个值,代表用户按下并触发了事件的鼠标按键。
返回值
含义
0
鼠标左键
1
鼠标中键
2
鼠标右键
测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
let test3 = document . querySelector ( '#test3' );
test3 . addEventListener ( 'mouseup' , function ( e ){
if ( e . button == 2 ) {
console . log ( 'click the right button' );
} else if ( e . button == 0 ){
console . log ( 'click the left button' );
} else if ( e . button == 1 ){
console . log ( 'click the middle button' );
} else {
console . log ( '????' );
}
});
参考文献