事件
一、事件流
事件流描述的是从页面中接收事件的顺序。IE的事件流是冒泡流,而Netscape的事件流是事件捕获流。
1) 事件冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。
2) 事件捕获:不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预订目标之前捕获它。
DOM事件流:DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
二、事件处理程序
1. HTML事件处理程序
在HTML中直接绑定事件:
<input type=’button’ value=’click me’ onclick=’alert("clicked")’ />
不能在其中使用未经转义的HTML语法字符,例如& “” < >
在函数内部,this值等于事件的目标元素
在HTML中指定事件处理程序有三个缺点:
时差问题,用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。因此很多HTML事件处理程序都会被封装在一个try-catch块中。
扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果。
HTML与JS代码紧密耦合。
2. DOM0级事件处理程序
btn.onclick=function(){ alert(this.id); //this指btn }
删除事件:btn.onclick=null;
3. DOM2级事件处理程序
addEventListener() removeEventListener()
接受3个参数:要处理的事件名、作为事件处理程序的函数,布尔值;
布尔值为true:捕获阶段调用事件处理程序; false:冒泡阶段调用事件处理程序。
btn.addEventListener(‘click’,handler,false);
使用DOM2级可以添加多个事件处理程序,并且会按照添加他们的顺序触发。
通过addEventListener()添加的事件必须通过removeEventListener()移除,并且参数一致,因此匿名函数将无法移除。
btn.removeEventListener(‘click’,handler,false);
如果是在匿名函数内部移除事件处理程序可以使用arguments.callee。如下所示:
EventUtil.addHandler(btn,'click',function(event){ //执行一些操作 EventUtil.removeHandler(btn,'click',arguments.callee); })
4. IE事件处理程序
attachEvent() detachEvent()
接受两个参数:事件处理程序名称,事件处理程序函数;
用于IE8及更早版本
btn.attachEvent(‘onclick’,handler);
使用attachEvent()时,事件处理程序会在全局作用域中运行,因此this等于window
attachEvent()也可以为一个元素添加多个事件,这些事件处理程序不是以添加他们的顺序执行,而是以相反的顺序被触发。
5. 跨浏览器的事件处理程序
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和removeHandler没有考虑到所有的浏览器问题,例如IE中的作用域问题。
三、事件对象
1. DOM中的事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
event对象包含于创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会有下表列出的成员。
1) currentTarget 事件处理程序当前正在处理事件的那个元素。
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。
2) detail 与事件相关的细节信息
3) preventDefault() 取消事件默认行为
4) stopPropagation() 取消事件的进一步捕获或冒泡。
5) target 事件的目标
6) type 被触发的事件类型
在需要通过一个函数处理多个事件时,可以使用type属性。eg:
var handler=function(event){ switch(event.type){ case ‘click’: case ‘mouseover’: case ‘mouseout’: } } btn.onclick=handler; btn.onmouseover=handler; btn.onmouseout=handler;
只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。
2. IE中的事件对象
要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。
1) 在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。
btn.onclick=function(){ var event=window.event; alert(event.type); }
2) 如果事件处理程序使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中。
btn.attachEvent(‘onclick’,function(event){ alert(event.type); })
在使用attachEvent()的情况下,也可以通过window对象来访问event对象,就像使用DOM0级方法时一样。
3)如果是通过HTML特性指定的事件处理程序,那么还可以通过一个名叫event的变量来访问event对象。
IE中所有事件对象都会包含下列属性和方法:
1)cancelBubble 默认为false,将其设置为true就可以取消事件冒泡(同stopPropagation())
2) returnValue 默认为true,将其设置为false就可以取消事件的默认行为(同preventDefault())
3) srcElement 事件的目标(同target)
4) type 被触发的事件类型。
因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为this会始终等于事件目标,故而,最好还是使用event.srcElement比较保险
3. 跨浏览器的事件对象
var EventUtil={ addHandler:function(element,type,handler){}, getEvent:function(event){ return event?event:window.event; }, getTarget:function(event){ return event.target||event.srcElement; }, preventDefault:function(event){ if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue=false; } }, removeHandler:function(element,type,handler){}, stopPropagation:function(event){ if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble=true; } } } 使用: btn.onclick=function(event){ alert(‘Clicked’); event=EventUtil.getEvent(event); //先获取跨浏览器的event对象 EventUtil.stopPropagation(event); //将event对象传入 }
四、事件类型
DOM3级事件规定了以下几类事件:
1) UI事件,用户与页面上的元素交互时触发;
2) 焦点事件,元素获得或失去焦点时触发;
3) 鼠标事件,用户通过鼠标在页面上执行操作时触发;
4) 滚轮事件,使用鼠标滚轮时触发
5) 文本事件,在文档中输入文本时触发
6) 键盘事件,用户通过键盘在页面上操作时触发
7) 合成事件,为IME输入字符时触发
8) 变动事件,底层DOM结构发生变化时触发
1. UI事件
DOMActivate;load;unload;abort;error;select;resize;scroll事件
除了DOMActivate之外,其他事件在DOM2级事件中都归为HTML事件,要确定浏览器是否支持DOM2级事件规定的HTML事件,可以使用以下代码:
var isSupported=document.implementation.hasFeature(‘HTMLEvents’,’2.0’);
要确定浏览器是否支持DOM3级事件定义的事件,可以使用:
var isSupported=document.implementation.hasFeature(‘UIEvent’,’3.0’);
1) load事件
当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上触发,当图像加载完毕时在<img>上触发,或者当嵌入的内容加载完毕时在<object>元素上触发;
JS中最常用的一个事件就是load。当页面完全加载后(包括所有图像,JS文件,CSS文件等外部资源),就会触发window上面的onload事件。有两种定义onload事件的处理程序方式:
第一种:
EventUtil.addHandler(window,’load’,function(event){})
这个event对象中不包含有关这个事件的任何附加信息,但在兼容DOM的浏览器中,event.target属性的值会被设置为document,而IE并不会为这个事件设置srcElement属性。
第二种:为<body>元素添加一个onload特性
图像上可以触发load事件,无论是在DOM中的图像元素还是HTML中的图像元素。
在创建新的<img>元素时,可以为其指定一个事件处理程序,以便图像加载完毕后给出提示。此时最重要的是要在指定src属性之前先指定事件。
EventUtil.addHandler(window,’load’,function(){ var img=document.createElement(‘img’); EventUtil.addHandler(img,’load’,function(event){ event=EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); }); document.body.appendChild(img); img.src=’smile.gif’; })
新图像不一定要从添加到文档后才开始下载,只要设置src属性就会开始下载。
同样的功能也可以通过使用DOM0级的Image对象实现。开发人员经常使用Image对象在客户端预先加载图像。可以像<img>元素一样使用Image对象,只不过无法将其添加到DOM树中。
EventUtil.addHandler(window,’load’,function(){ var image=new Image(); EventUtil.addHandler(image,’load’,function(event){ alert(‘image loader’); }); image.src=’smile.gif’; })
还有一些元素也以非标准的方式支持load事件。<script>元素也会触发load事件,以便确定动态加载的JS文件是否加载完毕。IE8及更早版本不支持<script>元素上的load事件。
IE和OPERA还支持<link>上的Load事件。
<script>和<link>类似,都是在未指定src或href属性并将<script>或<link>元素添加到文档之前不会开始下载样式表。
2) unload事件
这个事件是在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面就会发生unload事件。利用这个事件最多的情况就是清除引用,以避免内存泄漏。应该在body元素而非window对象上触发unload事件。
3)resize事件
当浏览器窗口被调整到一个新高度或宽度时,就会触发resize事件。这个事件在window上面触发,因此可以通过JS或者body元素中的onresize特性来指定事件处理程序。不要在这个事件的处理程序中加入大计算量的代码,因为这些代码可能会被频繁执行,从而导致浏览器反应明显变慢。
4)scroll事件
scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。混杂模式下:可以通过body元素的scrollLeft和scrollTop来监控这一变化;标准模式下除safari以外的所有浏览器都会通过html元素来反应这一变化(safari仍然基于body跟踪)
EventUtil.addHandler(window,’scroll’,function(event){ if(document.compatMode==’CSS1Compat’){ alert(document.documentElement.scrollTop); }else{ alert(document.body.scrollTop); } });
2.焦点事件
焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。
焦点事件:
blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
focus:在元素获得焦点时触发这个事件不会冒泡;所有浏览器都支持它。
focusin:元素获得焦点时触发,与focus等价,但他冒泡。支持的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome
focusout:元素失去焦点时触发。这个事件是HTML事件blur的通用版本。支持IE5.5+、Safari 5.1+、Opera 11.5+和Chrome
3. 鼠标与滚轮事件
9个鼠标事件:
click:用户单击主鼠标按钮或者按下回车键时触发。
dblclick:用户双击主鼠标按钮时触发。
mousedown:用户按下任意鼠标按钮时触发,不能通过键盘触发这个事件
mouseenter:鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。IE firefox9+ opera支持这个事件
mouseleave:位于元素上方的鼠标光标移动到元素范围之外时触发。不冒泡,光标移动到后代元素上不会触发。IE firefox9+ opera支持这个事件
mousemove:鼠标指针在元素内部移动时重复触发。不能通过键盘触发这个事件。
mouseout:鼠标指针位于一个元素上方,然后移入另一个元素时触发。移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发
mouseover:鼠标指针位于一个元素外部,首次移入另一个元素边界之内时触发。不能通过键盘触发
mouseup:用户释放鼠标按钮时触发。不能通过键盘触发。
使用以上事件要先检查浏览器是否支持DOM2级事件
var isSupported=document.implementation.hasFeature('MouseEvents','2.0');
要检测浏览器是否支持上面所有事件,可以如下检测:
var isSupported=document.implementation.hasFeature('MouseEvent','3.0'); //这里是MouseEvent 不是MouseEvents
1)客户区坐标位置
鼠标事件都是在浏览器视口中的特定位置上发生的。这个位置信息保存在事件对象的clientX和clientY属性中。所有浏览器都支持这两个属性,他们的值表示事件发生时鼠标指针在视口中的水平和垂直坐标。
使用类似下列代码取得鼠标事件的客户端坐标信息:
EventUtil.addHandler(div,'click',function(event){ event=EventUtil.getEvent(event); alert('client coordinates:'+event.clientX+','+event.clientY); })
当用户单击div所代表的元素时,就会看到事件的客户端坐标信息。注意,这些值中不包括页面滚动的距离,因此这个位置并不表示鼠标在页面上的位置。
2)页面坐标位置
通过客户区坐标能够知道鼠标是在视口中什么位置发生的,而页面坐标通过事件对象的pageX和pageY属性,能告诉你事件是在页面中什么位置发生的。这两个属性表示鼠标坐标在页面中的位置,因此坐标是从页面本身而非视口的左边和顶边计算的。
event.pageX和event.pageY来访问。但IE8及更早版本不支持对象上的页面坐标,因此可以利用客户区坐标和滚动信息来计算,如下所示:
EventUtil.addHandler(div,'click',function(event){ event=EventUtil.getEvent(event); var pageX=event.pageX; var pageY=event.pageY; if(pageX==undefined){ pageX=event.clientX+(document.body.scrollLeft||document.documentElement.scrollLeft); } if(pageY==undefined){ pageY=event.clientY+(document.body.scrollTop||document.documentElment.scrollTop); } })
3) 屏幕坐标位置
鼠标事件发生时,不仅会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。通过screenX和screenY属性就可以确定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。
4)修改键
修改键指的是:shift,ctrl,alt,meta
DOM为此规定了4个属性,表示这些修改键的状态:shiftKey,ctrlKey,altKey和metaKey.包含的都是布尔值,如果相应的键被按下了,则值为true,否则值为false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否同时按下了其中的键。
5)相关元素
对于mouseover事件而言,主目标是获得光标的元素,相关元素就是那个失去光标的元素。
DOM通过event对象的relatedTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值;对于其他事件,这个属性的值为null。IE8及之前的版本不支持relatedTarget属性,但提供了其他属性,mouseover事件触发时,IE的fromElement属性中保存了相关元素;在mouseout事件触发时,IE的toElement属性中保存着相关元素。
var EventUtil={ getRelatedTarget:function(event){ if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else{ return null; } } }
4. 键盘与文本事件
有3个键盘事件如下:
keydown用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
keypress用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下Esc键也会触发这个事件。
keyup当用户释放键盘上的键时触发。
键盘事件的对象中也有shiftKey、ctrlKey、altKey、metaKey属性。IE不支持meatKey
1) 键码 keyCode
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode属性的值与ASCII码中对应小写字母或数字的编码相同。
2) 字符编码 charCode
发生keypress事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入或删除字符的键都会触发keypress事件;
IE9 firefox chrome safari的event对象都支持一个charCode属性,这个属性只有在发生keypress事件时才包含值,而且这个值是按下的那个键所代表字符的ASCII编码。此时的keyCode通常等于0或者也可能等于所按键的键码。
要想跨浏览器获得字符编码,可以使用如下代码:
var EventUtil={ getCharCode:function(event){ if(typeof event.charCode==’number’){ return event.charCode; }else{ return event.keyCode; } } }
5. 变动事件
DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发,事件冒泡。
DOMNodeRemoved:在节点从其父节点中被移除时触发,事件冒泡。
IE8及更早版本不支持任何变动事件。
1) 删除节点
在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用。在这个事件触发时,节点尚未从其父节点删除,因此其parentNode属性仍然指向父节点。
2) 插入节点
在使用appendChild()、replaceChild()或insertBefore()向DOM中插入节点时,首先会触发DOMNodeInserted事件。这个事件的目标是被插入的节点,而event.relatedNode属性中包含一个对父节点的引用。在这个事件触发时,节点已经被插入到了新的父节点中。
6. HTML5事件
1) contextmenu事件
用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义菜单。
由于contextmenu事件是冒泡的,因此可以为document指定一个事件处理程序,用以处理页面中发生的所有此类事件。这个事件的目标是发生用户操作的元素。在所有浏览器中都可以取消这个事件:兼容DOM的浏览器中,使用event.preventDefault();在IE中,将event.returnValue的值设置为false。因为contextmenu事件属于鼠标事件,所以其事件对象中包含与光标有关的所有属性。通常使用contextmenu事件来显示自定义的上下文菜单,而使用onclick事件处理程序来隐藏该菜单。
<div id='myDiv'>Right Click me to get a custom context menu.</div> <ul id='myMenu' style='position:absolute;visibility:hidden;background-color:silver'> <li>Nicholas site</li> <li>wrox site</li> <li>yahoo</li> </ul> <script> EventUtil.addHandler(window,'load',function(event){ var div=document.getElementById('myDiv'); EventUtil.addHandler(div,'contextmenu',function(event){ event=EventUtil.getEvent(event); EventUtil.preventDefault(event); var menu=document.getElementById('myMenu'); menu.style.left=event.clientX+'px'; menu.style.top=event.clientY+'px'; menu.style.visibility='visible'; }); EventUtil.addHandler(document,'click',function(event){ document.getElementById('myMenu').style.visibility='hidden'; }); }) </script>
支持contextmenu事件的浏览器有IE,firefox,safari,chrome,opera11+;
2)beforeunload事件
发生在window对象上的beforeunload事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。
为了显示这个对话框,必须将event.returnValue的值设置为要显示给用户的字符串(对IE及firefox),同时作为函数的值返回(对safari和chrome)
EventUtil.addHandler(window,'beforeunload',function(event){ event=EventUtil.getEvent(event); var message='I\'m going to miss you if you go'; event.returnValue=message; return message; });
3)DOMContentLoaded事件
4)readystatechange事件
5)pageshow和pagehide事件
6)hashchange事件
方便在URL的参数列表(及URL中#号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在ajax应用中,开发人员经常要利用URL参数列表来保存状态或导航信息。
必须要把hashchange事件处理程序添加到window对象,然后URL参数列表只要变化就会调用它。使用location对象来确定当前的参数列表。
EventUtil.addHandler(window,'hashchange',function(event){ alert('current hash:'+location.hash); })
可以使用以下代码来检测浏览器是否支持hashchange 事件
var isSupported=('onhashchange' in window)&&(document.documentMode===undefined||document.documentMode>7);
7. 设备事件
1)orientationchange事件
方便开发人员能够确定用户何时将设备由横向查看模式切换到纵向查看模式。移动safari的window.orientation属性中可能包含3个值:0表示肖像模式,90表示向左旋转的横向模式,-90向右旋转的横向模式。
只要用户改变了设备的查看模式,就会触发orientationchange事件。此时的event对象不包含任何有价值的信息,因为唯一相关的信息可以通过window.orientation访问到。
EventUtil.addHandler(window,’load’,function(event){
var div=document.getElementById(‘myDiv’);
div.innerHTML=’current orientation is’+window.orientation;
EventUtil.addHandler(window,’orientationchange’,function(event){
div.innerHTML=’current orientation is’+window.orientation;
});
})
所有ios设备都支持orientationchange事件和window.orientation属性。
2)MozOrientation事件
3)deviceorientation事件
4)devicemotion事件
8.触摸与手势事件
1)触摸事件
2)手势事件
五、内存和性能
添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
1)事件委托
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。
如果可行的话,也可以考虑为document对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。这样做的好处是:
document对象很快就可以访问,而且可以在页面生命周期的任何时点上为他添加事件处理程序(无需等待DOMContentLoaded或load事件)。只要可单击的元素呈现在页面上,就可以立即具备适当的功能。
在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少。
整个页面占用的内存空间更少,能够提升整体性能。
最适合采用事件委托技术的事件包括click,mousedown,mouseup,keydown,keyup和keypress。
2)移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的连接数量。另外在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过时不用的空事件处理程序也是造成Web应用程序内存与性能问题的主要原因。
有两种情况可能造成上述问题:1.从文档中移除带有事件处理程序的元素时。常见的是使用innerHTML 替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当做垃圾回收。
<div id=’myDiv’> <input type=’button’ value=’click me’ id=’myBtn’> </div> <script> var btn=document.getElementById(‘myBtn’); btn.onclick=function(){ //执行某些操作 btn.onclick=null; document.getElementById(‘myDiv’).innerHTML=’Processing…’; } </script>
导致空事件处理程序的另外一种情况就是卸载页面的时候。如果在页面被卸载之前没有清理干净事件处理程序,那他们就会滞留在内存中。每次加载完页面再卸载页面时(可能是两个页面间来回切换,也可以是单击了刷新按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有被释放。
一般来说,最好的做法是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。在此,事件委托再次表现出它的优势——需要跟踪的事件处理程序越少,移除它们就越容易。对这种类似撤销的操作,我们可以把它想象成:只要是通过onload事件处理程序添加的东西,最后都要通过onunload事件处理程序将它们移除。
六、模拟事件
1.Dom中的事件模拟
1)模拟鼠标事件
2)模拟键盘事件
3)模拟其他事件
4)自定义DOM事件
2.IE中的事件模拟