事件
一、什么是事件:
事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。
浏览器赋予元素天生默认的一些行为,不论是否绑定相关的方法,只要进行相应的行为操作了,那么一定会触发相应的事件
事件是文档或者浏览器窗口中发生的,特定的交互瞬间。
事件是用户或浏览器自身执行的某种动作,如click,load和mouseover都是事件的名字。
事件是javaScript和DOM之间交互的桥梁。
你若触发,我便执行——事件发生,调用它的处理函数执行相应的JavaScript代码给出响应。
典型的例子有:页面加载完毕触发load事件;用户单击元素,触发click事件。
事件三要素:事件源(事件被触发的对象)、事件类型(什么事件,比如鼠标点击事件)和事件处理函数(针对某个事件要做出的相应)。
二、事件处理程序(事件处理器)
响应某个事件的函数就叫事件处理程序(也叫事件处理函数、事件句柄)。事件处理程序的名字以"on"开头,因此click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。
三、事件对象
什么是事件对象?
用我自己的话说,当某个事件触发时,浏览器就会创建一个对象,这个对象包含了该事件所有相关的属性和方法等有关信息,我们可以调用这个事件对象。
有人这么定义事件对象,说事件在浏览器中是以对象的形式存在的,触发一个事件,就会产生一个事件对象,该对象包含着所有与事件有关的信息,包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。这感觉就像是“手机”和“华为P7手机”的关系一样。
例如:鼠标操作产生的event中会包含鼠标位置的信息;键盘操作产生的event中会包含与按下的键有关的信息。
所有浏览器都支持event对象,但支持方式不同,在DOM中event对象必须作为唯一的参数传给事件处理函数,在IE中event是window对象的一个属性。
(一)、html事件处理程序中的event
会创建一个包含局部变量event的函数。可通过event直接访问事件对象。
<input id="btn" type="button" value="click" onclick=" console.log('html事件处理程序'+event.type)"/>
(二)、DOM中的事件对象
都会把event作为参数传入事件处理程序
DOM中事件对象重要的属性和方法。
属性:
-
- type属性,用于获取事件类型
- target属性 用户获取事件目标 事件加在哪个元素上。(主要在事件委托时使用)
方法:
-
- stopPropagation()方法 用于阻止事件冒泡
- preventDefault()方法 阻止事件的默认行为 移动端用的多
1 <body> 2 <input id="btn" type="button" value="click"/> 3 <script> 4 var btn=document.getElementById("btn"); 5 btn.onclick=function(event){ 6 console.log("DOM0 & click"); 7 console.log(event.type); //click 8 } 9 btn.addEventListener("click", function (event) { 10 console.log("DOM2 & click"); 11 console.log(event.type); //click 12 },false); 13 </script> 14 </body>
(三)、IE中的事件对象
第一种情况: 通过DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。
第二种情况:通过attachEvent()添加的事件处理程序,event对象作为参数传入。
<body> <input id="btn" type="button" value="click"/> <script> var btn=document.getElementById("btn"); btn.onclick= function () { var event=window.event; console.log(event.type); //click } </script> </body>
<body> <input id="btn" type="button" value="click"/> <script> var btn=document.getElementById("btn"); btn.attachEvent("onclick", function (type) { console.log(event.type); //click }) </script> </body>这里有个大大的问题没搞懂
(四)、事件对象的公共成员
1、DOM中事件对象的公共成员
事件对象包含与创建它有关的属性和方法。所以事件类型不一样,可用的属性和方法不一样。但是,DOM中所有事件都有以下公共成员。【注意bubbles属性和cancelable属性】
属性/方法 | 类型 | 读/写 | 说明 |
bubbles | Boolean | 只读 | 表明事件是否冒泡 |
stopPropagation()*** | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增) |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
preventDefault()*** | Function | 只读 | 取消事件的默认行为。如果cancelable是true,则可以使用这个方法 |
defaultPrevented | Boolean | 只读 | 为true表示已经调用了preventDefault()(DOM3级事件中新增) |
currentTarget*** | Element | 只读 | 事件处理程序绑定的那个元素(currentTarget始终===this,即处理事件的元素) |
target*** | Element | 只读 | 直接事件目标,真正触发事件的目标 |
detail | Integer | 只读 | 与事件相关的细节信息 |
eventPhase*** | Integer | 只读 | 调用事件处理程序的阶段:1表示捕获阶段,2表示处于目标阶段,3表示冒泡阶段 |
trusted | Boolean | 只读 | 为true表示事件是由浏览器生成的。为false表示事件是由开发人员通过JavaScript创建的(DOM3级事件中新增) |
type*** | String | 只读 | 被触发的事件的类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
a、对比currentTarget和target
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只是包含事件的实际目标。
举例:页面有个按钮,在body(按钮的父节点)中注册click事件,点按钮时click事件会冒泡到body进行处理。
1 <body> 2 <input id="btn" type="button" value="click"/> 3 <script> 4 document.body.onclick=function(event){ 5 console.log("body中注册的click事件"); 6 console.log("this===event.currentTarget? "+(this===event.currentTarget)); //true 7 console.log("currentTarget===document.body?"+(event.currentTarget===document.body)); //true 8 console.log('event.target===document.getElementById("btn")? '+(event.target===document.getElementById("btn"))); //true 9 } 10 </script> 11 </body>
b、通过type属性,可以在一个函数中处理多个事件。
原理:通过检测event.type属性,对不同事件进行不同处理。
举例:定义一个handler函数用来处理3种事件:click,mouseover,mouseout。
运行效果:点击按钮,弹出框。鼠标经过按钮,按钮背景色变为粉色;鼠标离开按钮,背景色恢复默认。
1 <body> 2 <input id="btn" type="button" value="click"/> 3 <script> 4 var handler=function(event){ 5 switch (event.type){ 6 case "click": 7 alert("clicked"); 8 break; 9 case "mouseover": 10 event.target.style.backgroundColor="pink"; 11 break; 12 case "mouseout": 13 event.target.style.backgroundColor=""; 14 } 15 }; 16 var btn=document.getElementById("btn"); 17 btn.onclick=handler; 18 btn.onmouseover=handler; 19 btn.onmouseout=handler; 20 </script> 21 </body>
c、stopPropagation()和stopImmediatePropagation()对比
异:二者的区别在于当一个事件有多个事件处理程序时,stopImmediatePropagation()可以阻止之后事件处理程序被调用。
举例:
1 <body> 2 <input id="btn" type="button" value="click"/> 3 <script> 4 var btn=document.getElementById("btn"); 5 btn.addEventListener("click",function(event){ 6 console.log("buttn click listened once"); 7 // event.stopPropagation();//取消注释查看效果 8 // event.stopImmediatePropagation();//取消注释查看效果 9 },false); 10 btn.addEventListener("click",function(){ 11 console.log("button click listened twice"); 12 },false); 13 document.body.onclick= function (event) { 14 console.log("body clicked"); 15 } 16 </script> 17 </body>
d、eventPhase
eventPhase值在捕获阶段为1,处于目标阶段为2,冒泡阶段为3。可以通过下面代码查看:
var btn=document.getElementById("btn"); btn.onclick= function (event) { console.log(event.CAPTURING_PHASE); //1 console.log(event.AT_TARGET); //2 console.log(event.BUBBLING_PHASE); //3 }
(2)、IE中event的公共成员
IE中的event的属性和方法和DOM一样会随着事件类型的不同而不同,但是也有一些是所有对象都有的公共成员,且这些成员大部分有对应的DOM属性或方法。
属性/方法 | 类型 | 读/写 | 说明 |
cancelBubble | Boolean | 读/写 | 默认为false,但将其设置为true就可以取消事件冒泡(与DOM中stopPropagation()方法的作用相同) |
returnValue | Boolean | 读/写 | 默认为true,但将其设置为false就可以取消事件的默认行为(与DOM中的preventDefault()方法的作用相同) |
srcElement | Element | 只读 | 事件的目标(与DOM中的target属性相同) |
type | String | 只读 | 被触发的事件的类型 |
(五)总结JS事件对象(event)的一些兼容性写法
获得event对象兼容性写法
event || (event = window.event);
获得target兼容型写法
event.target||event.srcElement
阻止浏览器默认行为兼容性写法
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
阻止冒泡写法
event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);
添加事件监听和取消事件监听
//添加事件监听兼容函数 function addHandler(target, eventType, handler){ if(target.addEventListener){//主流浏览器 addHandler = function(target, eventType, handler){ target.addEventListener(eventType, handler, false); }; }else{//IE addHandler = function(target, eventType, handler){ target.attachEvent("on"+eventType, handler); }; } //执行新的函数,如果不在这里执行,则智能添加一个处理程序 addHandler(target, eventType, handler); } //删除事件监听兼容函数 function removeHandler(target, eventType, handler){ if(target.removeEventListener){//主流浏览器 removeHandler = function(target, eventType, handler){ target.removeEventListener(eventType, handler, false); } }else{//IE removeHandler = function(target, eventType, handler){ target.detachEvent("on"+eventType, handler); } } //执行新的函数 removeHandler(target, eventType, handler); }
四、事件流:
定义:事件流是从页面中接收事件的顺序,事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流。
DOM标准规定事件流包括三个阶段:
-
- 事件捕获阶段;由根节点到达事件实际目标的阶段,实际目标在捕获阶段不会接收事件。
- 处于目标阶段;目标阶段事件处理会被看成是冒泡阶段的一部分。
- 事件冒泡阶段。事件又传播回文档。
注:
1)、尽管“DOM2级事件”标准规范明确规定事件捕获阶段不会涉及事件目标,但是在IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两次机会在目标对象上面操作事件。
2)、并非所有的事件都会经过冒泡阶段 。所有的事件都要经过捕获阶段和处于目标阶段,但是有些事件会跳过冒泡阶段:如,获得输入焦点的focus事件和失去输入焦点的blur事件。
两次机会在目标对象上面操作事件例子:
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <style> 8 #outer{ 9 position: absolute; 10 width: 400px; 11 height: 400px; 12 top:0; 13 left: 0; 14 bottom:0; 15 right: 0; 16 margin: auto; 17 background-color: deeppink; 18 } 19 #middle{ 20 position: absolute; 21 width: 300px; 22 height:300px; 23 top:50%; 24 left: 50%; 25 margin-left: -150px; 26 margin-top: -150px; 27 background-color: deepskyblue; 28 } 29 #inner{ 30 position: absolute; 31 width: 100px; 32 height:100px; 33 top:50%; 34 left:50%; 35 margin-left: -50px; 36 margin-top: -50px;; 37 background-color: darkgreen; 38 text-align: center; 39 line-height: 100px; 40 color:white; 41 } 42 #outer,#middle,#inner{ 43 border-radius:100%; 44 } 45 </style> 46 <body> 47 <div id="outer"> 48 <div id="middle"> 49 <div id="inner"> 50 click me! 51 </div> 52 </div> 53 </div> 54 <script> 55 var innerCircle= document.getElementById("inner"); 56 innerCircle.addEventListener("click", function () { 57 alert("innerCircle的click事件在捕获阶段被触发"); 58 },true); 59 innerCircle.addEventListener("click", function () { 60 alert("innerCircle的click事件在冒泡阶段被触发"); 61 },false); 62 var middleCircle= document.getElementById("middle"); 63 middleCircle.addEventListener("click", function () { 64 alert("middleCircle的click事件在捕获阶段被触发"); 65 },true); 66 middleCircle.addEventListener("click", function () { 67 alert("middleCircle的click事件在冒泡阶段被触发"); 68 },false); 69 var outerCircle= document.getElementById("outer"); 70 outerCircle.addEventListener("click", function () { 71 alert("outerCircle的click事件在捕获阶段被触发"); 72 },true); 73 outerCircle.addEventListener("click", function () { 74 alert("outerCircle的click事件在冒泡阶段被触发"); 75 },false); 76 </script> 77 </body> 78 </html>
运行效果就是会陆续弹出6个框,为说明原理我整合成了一个图:
五、事件类型
(一)、按级别划分:
1、HTML事件
事件直接加在html元素上。
首先,这种方法已经过时了。因为动作(javascript代码)和内容(html代码)紧密耦合,修改时即要修改html也要修改js。但是写个小demo的时候还是可以使用的。
这种方式也有两种方法,都很简单:
第一种:直接在html中定义事件处理程序及包含的动作。
<input type="button" value="click me!" onclick="alert('clicked!')"/>
第二种:html中定义事件处理程序,执行的动作则调用其他地方定义的脚本。
1 <input type="button" value="click me!" onclick="showMessage()"/> 2 <script> 3 function showMessage(){ 4 alert("clicked!"); 5 } 6 </script>
注:
1)通过event变量可以直接访问事件本身,比如onclick="alert(event.type)"会弹出click事件。
2)this值等于事件的目标元素,这里目标元素是input。比如 onclick="alert(this.value)"可以得到input元素的value值。
2、DOM0级事件(无法绑定多个处理函数,后定义的处理函数会覆盖前面定义的函数)
绑定事件:element.onclick=function(){}
解绑事件:element.onclick=null;
3、DOM1没有跟事件相关的更新
4、DOM2级事件(可以绑定多个处理函数,所有的DOM节点都包含这2个方法。)
绑定事件:element.addEventListener('click', 函数名, false);
解绑事件:element.removeEventListener('click', 函数名, false);
function 函数名(){};
注:通过addEventListener添加的匿名函数将无法删除。。
为了能删除事件处理程序,代码可以这样写:
<input id="myBtn" type="button" value="click me!"/> <script> var myBtn=document.getElementById("myBtn"); var handler=function(){ alert("hello"); } myBtn.addEventListener("click",handler,false); myBtn.removeEventListener("click",handler,false); </script>
5、DOM3级事件 (定义了自定义事件)
element.addEventListener('keyup', function(){}, false)
(二)、按事件类型划分
1、鼠标事件
- click:用户单击主鼠标按钮(一般是左键)或者按下在聚焦时按下回车键时触发
- dblclick:用户双击主鼠标按键触发(频率取决于系统配置)
- mousedown:用户按下鼠标任意按键时触发
- mouseup:用户抬起鼠标任意按键时触发
- mousemove:鼠标在元素上移动时触发
- mouseover:鼠标进入元素时触发
- mouseout:鼠标离开元素时触发
- mouseenter:鼠标进入元素时触发,该事件不会冒泡
- mouseleave:鼠标离开元素时触发,该事件不会冒泡
区别:
over和out,不考虑子元素,从父元素移动到子元素,对于父元素而言,仍然算作离开
enter和leave,考虑子元素,子元素仍然是父元素的一部分
mouseenter和mouseleave不会冒泡
事件对象
所有的鼠标事件,事件处理程序中的事件对象,都为 MouseEventaltKey:触发事件时,是否按下了键盘的alt键
ctrlKey:触发事件时,是否按下了键盘的ctrl键
shiftKey:触发事件时,是否按下了键盘的shift键
button:触发事件时,鼠标按键类型
0:左键
1:中键
2:右键
位置
page:pageX、pageY,当前鼠标距离页面的横纵坐标
client: clientX、clientY,鼠标相对于视口的坐标
offset:offsetX、offsetY,鼠标相对于事件源的内边距的坐标
screen: screenX、screenY,鼠标相对于屏幕
x、y,等同于clientX、clientY
movement:movementX、movementY,只在鼠标移动事件中有效,相对于上一次鼠标位置,偏移的距离
2、键盘事件
3、表单事件
4、html5事件
5、window全局对象
IE事件兼容问题
IE8及以下浏览器不支持addEventListener,在实际开发中如果要兼容到IE8及以下浏览器。如果用原生的绑定事件,需要做兼容处理,可利用jquery的bind代替。
IE8及以下版本浏览器实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。
这两个方法都需要两个参数:事件处理程序名称和事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。注意是事件处理程序名称而不是事件名称,所以要加上on,是onclick而不是click。
注:
IE11只支持addEventListener!
IE9,IE10对attachEvent和addEventListener都支持!
TE8及以下版本只支持attachEvent!
可以拿下面代码在IE各个版本浏览器中进行测试。
1 <input id="myBtn" type="button" value="click me!"/> 2 <script> 3 var myBtn=document.getElementById("myBtn"); 4 var handlerIE=function(){ 5 alert("helloIE"); 6 } 7 var handlerDOM= function () { 8 alert("helloDOM"); 9 } 10 myBtn.addEventListener("click",handlerDOM,false); 11 myBtn.attachEvent("onclick",handlerIE); 12 </script>
添加事件处理程序:现在为按钮添加两个事件处理函数,一个弹出“hello”,一个弹出“world”
1 <script> 2 var myBtn=document.getElementById("myBtn"); 3 myBtn.attachEvent("onclick",function(){ 4 alert("hello"); 5 }); 6 myBtn.attachEvent("onclick",function(){ 7 alert("world"); 8 }); 9 </script>
注:这里运行效果值得注意一下:
IE8以下浏览器中先弹出“world”,再弹出“hello”。和DOM中事件触发顺序相反。
IE9及以上浏览器先弹出“hello”,再弹出“world”。和DOM中事件触发顺序相同了。
可见IE浏览器慢慢也走上正轨了。。。
删除事件处理程序:通过attachEvent添加的事件处理程序必须通过detachEvent方法删除,且参数一致。
和DOM事件一样,添加的匿名函数将无法删除。
所以为了能删除事件处理程序,代码可以这样写:
1 <input id="myBtn" type="button" value="click me!"/> 2 <script> 3 var myBtn=document.getElementById("myBtn"); 4 var handler= function () { 5 alert("hello"); 6 } 7 myBtn.attachEvent("onclick",handler); 8 myBtn.detachEvent("onclick",handler); 9 </script>
note:IE事件处理程序中还有一个地方需要注意:作用域。
使用attachEvent()方法,事件处理程序会在全局作用域中运行,因此this等于window。
而dom2或dom0级的方法作用域都是在元素内部,this值为目标元素。
下面例子会弹出true。
<input id="myBtn" type="button" value="click me!"/> <script> var myBtn=document.getElementById("myBtn"); myBtn.attachEvent("onclick",function(){ alert(this===window); }); </script>
在编写跨浏览器的代码时,需牢记这点。
IE7\8检测:
1 //判断IE7\8 兼容性检测 2 var isIE=!!window.ActiveXObject; 3 var isIE6=isIE&&!window.XMLHttpRequest; 4 var isIE8=isIE&&!!document.documentMode; 5 var isIE7=isIE&&!isIE6&&!isIE8; 6 7 if(isIE8 || isIE7){ 8 li.attachEvent("onclick",function(){ 9 _marker.openInfoWindow(_iw); 10 }) 11 }else{ 12 li.addEventListener("click",function(){ 13 _marker.openInfoWindow(_iw); 14 }) 15 }
焦点事件
鼠标事件
六、事件代理(事件委托)
传统的事件处理中,需要为每个元素添加事件处理器。js事件代理则是一种简单有效的技巧,通过它可以把事件处理器添加到一个父级元素上,从而避免把事件处理器添加到多个子级元素上。
1、事件代理
事件代理的原理用到的就是事件冒泡和目标元素,把事件处理器添加到父元素,等待子元素事件冒泡,并且父元素能够通过target(IE为srcElement)判断是哪个子元素,从而做相应处理。
传统事件处理,为每个元素添加事件处理器,代码如下:
1 <body> 2 <ul id="color-list"> 3 <li>red</li> 4 <li>orange</li> 5 <li>yellow</li> 6 <li>green</li> 7 <li>blue</li> 8 <li>indigo</li> 9 <li>purple</li> 10 </ul> 11 <script> 12 (function(){ 13 var colorList=document.getElementById("color-list"); 14 var colors=colorList.getElementsByTagName("li"); 15 for(var i=0;i<colors.length;i++) 16 { 17 colors[i].addEventListener('click',showColor,false); 18 }; 19 function showColor(e) 20 { 21 e=e||window.event; 22 var targetElement=e.target||e.srcElement; 23 alert(targetElement.innerHTML); 24 } 25 })(); 26 </script> 27 </body>
事件代理的处理方式,代码如下:
1 <body> 2 <ul id="color-list"> 3 <li>red</li> 4 <li>orange</li> 5 <li>yellow</li> 6 <li>green</li> 7 <li>blue</li> 8 <li>indigo</li> 9 <li>purple</li> 10 </ul> 11 <script> 12 (function(){ 13 var colorList=document.getElementById("color-list"); 14 colorList.addEventListener('click',showColor,false); 15 function showColor(e) 16 { 17 e=e||window.event; 18 var targetElement=e.target||e.srcElement; 19 if(targetElement.nodeName.toLowerCase()==="li"){ 20 alert(targetElement.innerHTML); 21 } 22 } 23 })(); 24 </script> 25 </body>
2、事件代理的好处
总结一下事件代理的好处:
- 将多个事件处理器减少到一个,因为事件处理器要驻留内存,这样就提高了性能。想象如果有一个100行的表格,对比传统的为每个单元格绑定事件处理器的方式和事件代理(即table上添加一个事件处理器),不难得出结论,事件代理确实避免了一些潜在的风险,提高了性能。
- DOM更新无需重新绑定事件处理器,因为事件代理对不同子元素可采用不同处理方法。如果新增其他子元素(a,span,div等),直接修改事件代理的事件处理函数即可,不需要重新绑定处理器,不需要再次循环遍历。
3、事件代理的问题:
代码如下:事件代理同时绑定了li和span,当点击span的时候,li和span都会冒泡。
<li><span>li中的span的内容</span></li> <script> $(document).on('click', 'li', function(e){ alert('li li'); }); $(document).on('click', 'span', function(e){ alert('li span'); }) </script>
解决办法:
方法一:span的事件处理程序中阻止冒泡
$(document).on('click', 'span', function(e){ alert('li span'); e.stopPropagation(); })
方法二:li的事件处理程序中检测target元素
$(document).on('click', 'li', function (e) { if (e.target.nodeName == 'SPAN') { e.stopPropagation(); return; } alert('li li'); });
4、事件代理的一个有趣应用
点击一个列表时,输出对应的索引
<script> var ul=document.querySelector('ul'); var lis=ul.querySelectorAll('ul li'); ul.addEventListener('click', function (e) { var target= e.target; if(target.nodeName.toUpperCase()==='LI'){ alert([].indexOf.call(lis,target)); } },false) </script>
举例:
页面加载事件
JS:window.onload = function(){};
-----函数表达式结构创建函数
当页面所有资源加载完毕之后执行,无法同时绑定多个处理程序–DOM0级
DOM2级加载:
document.addEventListener("DOMContentLoaded",function(){ alert(2) },false);