事件流
事件流:从页面接收事件的顺序。
DOM2级事件流:包括三个阶段,分别是事件捕获阶段,处于目标阶段和事件冒泡阶段。其中事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件,而事件冒泡的思想恰好相反。
过程如图:
事件处理程序:响应某个事件的函数。
接下我们讲一下指定事件处理程序的几种方法。
HTML事件处理程序
直接看代码
<input type="button" value="Click Me" onclick="alert('Clicked')" />
//或者是调用页面其他地方定义的脚本
<script type="text/javascript">
function showMessage(){
alert("Clicked");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
在这个函数内部,this就是事件的目标元素。
有3个问题:
时差问题,可能在页面解析js代码之前就触发了事件,就会引发错误,所以我们通常用try-catch封装代码。
利用这种方式扩展事件处理程序的作用域链在不同浏览器中结果不同。
html与js代码紧密耦合,修改太不方便。
DOM0级事件处理程序
页面中每一个元素都有自己的事件处理程序属性,所以我们可以将一个函数赋值给一个事件处理程序的属性来为事件指定处理程序。这是一种传统方式。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};
btn.onclick = null; //删除
此时,事件处理程序运行在元素的作用域中,this.id == myBtn
DOM2级事件处理程序
DOM2级事件定义了两个方法来做这件事。
/*addEventListener(要处理的事件名,事件处理程序函数,是在捕获还是冒泡阶段调用); 其中:true:捕获,false:冒泡
removeEventListener(要处理的事件名,事件处理程序函数,是在捕获还是冒泡阶段调用);*/
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
此时,事件处理程序也运行在元素的作用域中,this.id == myBtn
不过这种方法有一个问题,那就是通过addEventListener方法添加的匿名函数无法移除,只有将函数的引用传给对应的remove方法才行。因为js中函数也是对象,每次创建一个function就是创建了一个新的对象。
IE事件处理程序
与DOM2级类似,有两个方法。
/*
attachEvent(事件处理程序名,事件处理程序函数);
detachEvent(事件处理程序名,事件处理程序函数);
所有事件都被添加到冒泡阶段
*/
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("Clicked");
});
特别的是,这种情况下指定的事件处理程序是在全局作用域中运行的,此时的this == window (true)
另外和上面的DOM还有两个不同点,一个是事件处理程序的名称此时传入的是onclick而不是上面的click,另一个是若是为一个元素添加两个不同的事件处理程序,DOM方法是以添加他们的顺序执行,而IE是以相反的顺序被触发。
相同的是,匿名函数依旧不能移除,原因上面解释过了。
到这里,关于事件处理程序的指定就告一段落,我们给出一个扩浏览器的事件处理程序
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{
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}
else if(element.detachEvent){
element.detachEvent("on"+type,handler);
}
else{
element["on"+type] = null;
}
}
};
事件对象
在触发事件时就会产生一个对应的事件对象,该对象包含着所有与事件有关的信息。
DOM中的事件对象
兼容DOM的浏览器会将一个event对象传入到事件处理程序中。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type); //click
};
btn.addEventListener("click",function(event){
alert(event.type); //click
},false);
在这里,我列出所有事件都公有的常用的一些属性和方法
属性/方法 | 说明 |
---|---|
target | 事件的目标 |
type | 事件类型 |
currentTarget | 该处理程序当前正在处理事件的那个元素 |
preventDefault() | 取消事件的默认行为 |
eventPhase | 调用事件处理程序的阶段:1-捕获,2-处于目标,3-冒泡 |
stopPropagation() | 取消事件的进一步事件捕获或者冒泡 |
有几点我们要知道的。
this始终等于currentTarget的值。
当eventPhase=2时,this == currentTarget == target
只有在事件处理程序的执行期间,event对象才存在。
IE中的事件对象
访问IE中的事件对象,有几种不同的方法。
- 通过DOM0级方法添加事件处理时,event对象作为window对象的一个属性存在。
- 通过attachEvent()添加的,event对象作为参数传入处理函数中。也可以通过window对象来访问event。
- 通过HTML特性指定的,可通过一个名为event的变量来访问。
属性/方法 | 说明 |
---|---|
srcElement | 事件的目标(与DOM中的target相同) |
type | 事件类型 |
returnValue | 默认为true,为false时表示取消事件的默认行为(与DOM中的preventDefault()作用相同) |
cancelBubble | 默认为false,为true时表示可以取消事件冒泡(与DOM中的stopPropagation()作用相同) |
注意一点,由于事件处理程序的作用域是根据它指定的方式来确定的,所以this值并不一定都等于事件目标,所以建议使用srcElement属性。
可以按照上面的方法,对EventUtil对象加以增强,来实现对扩浏览器的事件对象的访问。在这里我就不附上代码了。
事件委托
为了提升页面的性能,我们可以利用事件委托。事件委托利用了事件冒泡,通过指定一个事件处理程序,就可以管理某一类型的所有事件。
直接看代码:
<ul id="myUl">
<li id="goSomewhere">Go Somewhere</li>
<li id="doSomething">Do Something</li>
<li id="sayHi">Say Hi</li>
</ul>
<script>
var list = document.getElementById("myUl");
EventUtil.addHandler(list,"click",function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
document.title="I changed the document's title";
break;
case "goSomewhere":
location.href = "http://";
break;
case "sayHi":
alert("hi");
break;
}
});
</script>
事件委托的几大优点有:
document对象很快就可以访问,而且可以在页面生命周期的任何时间点上为它添加事件处理程序。
在页面中设置事件处理程序所需的时间更少。
整个页面所占的内存空间更少,提升整体性能。
讲到性能,顺便提一下,造成web应用程序内存与性能问题的一个主要原因,就是内存中那些过时不用的“空事件处理程序”。主要有两种情况会造成“空事件处理程序”。一个是从文档中移除带有事件处理程序的元素时,如果不先移除元素上绑定的事件处理程序,就会形成这个问题。另一个是卸载页面的时候,如果在页面卸载之前没有清理干净事件处理程序,则它们会长期滞留在内存中。最好是在卸载页面之前通过onunload事件处理程序移除所有事件处理程序。
到这里,关于js中的事件这一部分就说完了。