js事件系列

174 阅读9分钟

一、事件的概念

事件指文档或者浏览器窗口中发生的一些特定的交互。具体来说就是鼠标点击、键盘输入等用户操作。

二、事件三要素

可以分为事件源、事件类型和事件对象;

事件源也是指事件元素。指的是用户发生操作的那个元素。

事件类型是指鼠标点击事件、键盘事件、滚动事件等等;

事件对象是指当某个事件触发时产生的对象。不同事件产生的事件对象不同。

事件对象的兼容:var e=evt||event;

三、事件流

概念:当某个事件执行时,从子元素向父元素触发或者从父元素向子元素触发称为事件流。

事件流分为三个阶段:事件捕获阶段、目标阶段和事件冒泡阶段。在DOM中,实际的目标在捕获阶段不会接受到事件,所以在捕获阶段,事件从document到html再到body后就停止了。下一个阶段是处于目标阶段,于是事件在div上发生,并在事件处理中被看成冒泡阶段的一部分。

分为事件冒泡和事件捕获。

四、事件冒泡

概念:事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。比如事件是由div接受的,然后向上冒泡,到body、html、document、window。

注意:并不是所有事件都会产生冒泡问题, onfocus onblur onload不会产生冒泡问题

五、事件捕获

概念:由父元素向子元素触发。也就是由顶层的元素向下传播,最具体的元素最后接受到事件。与事件冒泡的顺序正好相反。

六、事件处理程序

概念:响应某个事件的函数叫做事件处理程序

1、DOM0级事件处理程序

通过js指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。

<button id="btn">点击</button>  
var toBtn=document.getElementById("btn"); 
 toBtn.onclick=function(){    
 console.log(this.id) //btn 
 }

这种方法被认为是元素的方法,这种情况下事件处理程序是在元素的作用域中运行。也就是this引用的当前元素。通过this可以访问元素的所有属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。并且可以删除通过DOM0级方法指定的事件处理程序。

toBtn.onclick=null;

2、DOM2级事件处理程序

"DOM2"定义了两个方法。用于处理指定和删除事件程序的操作。addEventListener()和removeEventListener()。它有三个参数。分别是事件名、事件处理程序和一个布尔值。这个布尔值如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

<button id="btn">点击</button>
var toBtn=document.getElementById("btn");
toBtn.addEventListener('click',function(){console.log(this.id)},false)//btn

使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。

通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加程序时使用的参数相同。注意的是通过addEventListener()添加的匿名函数将无法移除。

3、IE事件处理程序

IE中与DOM中类似的两个方法:attachEvent()和detachEvent()。两个方法接受相同的两个参数:事件处理程序与事件处理程序函数名。IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。

<button id="btn">点击</button>
var toBtn=document.getElementById("btn");
 toBtn.attachEvent('onclick',function(){   
  console.log('click')//click 
 })

在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this等于window。

<button id="btn">点击</button>
var toBtn=document.getElementById("btn");
 toBtn.attachEvent('onclick',function(){   
  console.log(this===window)//true
})

与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。不过与DOM方法不一样,IE中添加的事件处理程序不是以添加它们顺序执行,而是以相反的顺序被触发。

4、跨浏览器的事件处理程序

var EventUtil = {

    addHandler:function (element,type,handler) {
         if(element.addEventListener){
             element.addEventListener(type, handler,false);
         } else if (element.attachEvent) {
             element.attachEvent("on" + type,handler);
         }else {
             element["on" + type] = handler;
         }
    },

    removeHandler:function (element,type,handler) {
         if (element.removeEventListener) {
             element.removeEventListener(type, handler,false);
         } else if (element.detachEvent) {
             element.detachEvent("on" + type,handler);
         }else {
             element["on" + type] = null;
         }
    }
};

创建addHandler方法,来处理DOM0、DOM2和IE方法来添加事件;removeHandler来移除添加的事件处理程序。

var btn = document.getElementById("myBtn");
var handler = function () {
     alert("Clicked");
};

EventUtil.addHandler(btn,"click",handler);

EventUtil.removeHandler(btn,"click",handler);

七、事件对象

触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。

1、DOM中的事件对象

兼容DOM的浏览器将一个event对象传入到事件处理程序中。

 var toBtn=document.getElementById("btn");    toBtn.onclick=function(event){     console.log(event.type) //click  }  toBtn.addEventListener('click',function(event){     console.log(event.type) //click     },false)

图片来自网络侵删

详细属性和方法链接:www.cnblogs.com/lbnnbs/p/66…

在事件处理程序内部,对象this等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。

  var toBtn=document.getElementById("btn");    toBtn.onclick=function(event){     console.log(event.target===this); //true     console.log(event.currentTarget===this)//true  }

如果事件处理程序存在于按钮的父节点上,这些值是不相等的。

  var toBtn=document.getElementById("btn");    document.body.onclick=function(event){     console.log(event.target===toBtn); //true     console.log(event.currentTarget===this)//true     console.log(this==document.body)  }

注意:只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。

2、IE中的事件对象

DOM级方法添加事件处理程序,event作为window对象的一个属性存在。

  var toBtn=document.getElementById("btn");    toBtn.onclick=function(){
     var event=window.event;
      console.log(event.type) //click  }

事件处理程序是使用attachEvent()添加的,就会有一个event对象作为参数传入函数中。

<button id="btn">点击</button>
var toBtn=document.getElementById("btn");
 toBtn.attachEvent('onclick',function(event){   
  console.log(event.type) //click
})

因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为this 会始终等于事件目标。故而,最好还是使用event.srcElement 比较保险。例如:

btn.onclick = function(){
alert(window.event.srcElement === this); //true
};
btn.attachEvent(“onclick”, function(event){
alert(event.srcElement === this); //false
});

3、跨浏览器的事件对象

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<button id="btn">Click me</button>
	<script>
		var EventUtil = {
			//添加DOM方法
			addHandler:function(element,type,handler){
				//是否支持DOM2级方法
				if(element.addEventListener){
					element.addEventListener(type,handler,false);
				}
				//兼容IE8以及更早版本
				else if(element.attachEvent){
					element.attachEvent('on'+type,handler);
				}
				//DOM0级方法
				else{
					element['on'+click] = handler;
				}
			},
			//删除DOM方法
			removeHandler:function(element,type,handler){
				if(element.removeEventListener){
					element.removeEventListener(type,handler,false);
				}else if(element.detachEvent){
					element.detachEvent('on'+type,handler);
				}else{
					element['on'+type] = null;
				}
			},
			//获得事件对象event
			getEvent:function(event){
				return event || window.event;
			},
			//获得事件目标
			getTarget:function(event){
				return event.target || event.srcElement;
			},
			//取消事件冒泡
			stopPropagation:function(event){
				if(event.stopPropagation){
					event.stopPropagation();
				}else{
					//兼容IE以及低版本
					event.cancleBubble = true;
				}
			},
			//取消默认行为
			preventDefault:function(event){
				if(event.preventDefault){
					event.preventDefault();
				}else{
					event.returnValue = false;
				}
			}
		};
		var btn = document.getElementById('btn');
		btn.onclick = function(event){
			alert('clicked');
			//获取事件对象
			event = EventUtil.getEvent(event);
			//获取对象目标
			var target = EventUtil.getTarget(event);
			//取消事件冒泡,从而'body clicked 不会弹出'
			EventUtil.stopPropagation(event);
		};
		document.body.onclick = function(){
			alert('body clicked!');
		}
	</script>
</body>
</html>

八、事件触发器(Event emitter)

事件派发器是一种模式,它监听一个已命名的事件,触发回调,然后发出该事件并附带一个值。这被称为“发布/订阅”模型或监听器。

发布者订****阅者模式

也可以称之为消息机制,定义了一种依赖关系,这种依赖关系可以理解为 1对N (注意:不一定是1对多,有时候也会1对1),观察者们同时监听某一个对象相应的状态变换,一旦变化则通知到所有观察者,从而触发观察者相应的事件,该设计模式解决了主体对象与观察者之间功能的耦合

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。

//引入events模块
var events=require('events');
//创建eventEmitter对象
var eventEmitter=new events.EventEmitter();
eventEmitter.on('say',function(){
  console.log('事件触发')
})
setTimeout(()=>{
eventEmitter.emit('say')},1000)

运行这段代码,1 秒后控制台输出了 ' 事件触发'。其原理是 event 对象注册了事件 say 的一个监听器,然后我们通过 setTimeout 在 1000 毫秒以后向 event 对象发送事件say,此时会调用say 的监听器。

EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。

当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

运行顺序:

emit(发布)——apply或者call(内部运行)——on(订阅)

注意:emit触发事件,并将参数传给事件的处理函数;

on:监听event,获得emit传递的函数,触发时调用callback函数。

参考链接:www.runoob.com/nodejs/node…

参考链接:www.cnblogs.com/penghuwan/p…

参考链接:juejin.cn/post/684490…

 componentDidMount() {        //注册监听动画的事件        this.eventEmitter = this.rootProps.emitter.addListener('eventAni', this.eventAni);        //注册监听tab切换的事件        this.eventEmitter = this.rootProps.emitter.addListener('tabChange', this.tabChange);    }

emit可应用于点击事件或者其他任何函数中

this.props.rootProps.emitter.emit('eventAni', { eventType: 'tabChangeScroll', parentId: this.props.id, currIndex: index })

九、自定义事件

js 一般事件像是click、blur、focus等等。除了这些之外还可以自己定义事件,但是自定义事件同样需要自己定义触发机制。

简单的创建事件方法,一般用Event构造器;

var event=new Event('eventName',{"bubbles":true,"cancelable":false})
document.dispatchEvent(event);

如果传递数据的话,需要使用CustomEvent构造器;

var myEvent = new CustomEvent('event_name', {
    detail:{
        // 将需要传递的数据写在detail中,以便在EventListener中获取
        // 数据将会在event.detail中得到
    },
});

事件的监听

JS的EventListener是根据事件的名称来进行监听的,比如我们在上文中已经创建了一个名称为**‘event_name’** 的事件,那么当某个元素需要监听它的时候,就需要创建相应的监听器:

//假设listener注册在window对象上window.addEventListener('event_name',function(event){
// 如果是CustomEvent,传入的数据在event.detail中
    console.log('得到数据为:', event.detail);})

window对象上就有了对**‘event_name’** 这个事件的监听器,当window上触发这个事件的时候,相关的callback就会执行。

事件的触发

if(window.dispatchEvent){
   window.dispatchEvent(myEvent)
}else{
 window.fireEvent(myEvent)
}

demo:

<!DOCTYPE html><html>  <head lang="zh-CN">    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1" />    <title></title>    <style>      .button {        width: 500px;        height: 500px;        background-color: antiquewhite;        margin: 10px;        text-align: center;        line-height: 500px;      }    </style>  </head>  <body>    <div class="button">Button</div>    <script>      //创建封装      if (!window.CustomEvent) {        window.CustomEvent = function (type, config) {          config = config || {            bubbles: false,            cancelable: false,            detail: undefined,          };          var e = document.createEvent("CustomEvent");          e.initCustomEvent(            type,            config.bubbles,            config.cancelable,            config.detail          );          return e;        };        window.CustomEvent.prototype = window.Event.prototype;      }      var btn = document.querySelector(".button");      var ev = new CustomEvent("testEvent", {        bubbles: "true",        cancelable: "true",        detail: "yuanhl",      });      btn.addEventListener(        "testEvent",        function (event) {
          console.log(event.detail);

          console.log(event.bubbles);          console.log(event.cancelable);        },        false      );      btn.dispatchEvent(ev);    </script>  </body></html>

参考资料:developer.mozilla.org/zh-CN/docs/…

十、事件委托

事件委托:某个事件让其他元素来完成

委托的好处:
1\. 把某个事件加到父元素上,提高程序的执行效率
2\. 动态创建的元素可以在创建元素的函数体外部为其添加事件
委托的机制:
利用事件冒泡(常见) 或者 事件捕获

不是所有事件都可以实现事件委托 常见到也就那么几个 :click、mousedown、mouseup、keydown、keyup和keypress

      <ul id="ul1">         <li>111</li>         <li>222</li>         <li>333</li>         <li>444</li>      </ul>window.onload = function () {         var oUl = document.getElementById("ul1");         oUl.onclick = function (ev) {            var ev = ev || window.event;            var target = ev.target || ev.srcElement;            if (target.nodeName.toLowerCase() == 'li') {                 alert(target.innerHTML);            }         }      }