在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 ( '????' ); 
  } 
}); 
 
 
 
 参考文献