Javascript事件
事件流
事件流描述的是从页面中接收事件的顺序。但是IE 和 Netscape开发团队提出了两个完全相反的事件流概念,IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时由具体的元素接收,然后逐级向上传播到较为不具体的节点。所有的现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别的。IE5.5及更早的版本冒泡会跳过<html>元素。IE9、Firefox、chrome、safari则将事件一直冒泡到window对象。
事件捕获
事件捕获的思想是不太具体的节点应该更早的接收到时间,而具体的节点应该最后的接收事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
Dom事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的是事件捕获,为截取事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
事件处理程序
事件就是用户或浏览器自身执行的某些动作。而响应某个事件的函数就叫做事件处理程序。
DOM0级事件处理程序
var btn = document.getElementById('mybtn');
btn.onclick = function () {
alert('Clicked');
};
这就是DOM0级指定事件处理程序。但是要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。
使用DOM0级方法指定的事件处理程序被认为是元素方法,因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中this引用当前元素。
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert (this.id);
}
也可以删除通过DOM0级方法指定的事件处理程序。
btn.onclick = null;
DOM2 级事件处理程序
“DOM2 级”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有的DOM节点中都包含这两个方法,并且他们都接收三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
var btn = document.getElementById('mybtn');
btn.addEventListener("click",function(){
alert(this.id);
},false);
上面的代码为一个按钮添加了 onclick 事件处理程序,而且该事件会在冒泡阶段被触发。与DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
var btn = document.getElementById('mybtn'){
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.addEventListener("click",function(){
alert("Hello World!");
},false);
这里为按钮添加了两个事件处理程序。这两个事件处理程序会按照添加他们的顺序触发。
通关addEventListener()添加的事件处理程序只能用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。
IE事件处理程序
IE中的事件处理程序有两个方法:attachEvent()和detachEvent(),两个方法接收的参数相同:事件处理程序名称和事件处理函数。由于IE8及更早的版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
var btn = document.getElementById('mybtn');
btn.attachEvent("onclick",function () {
alert("clicked");
});
注意attachEvent的第一个参数是“onclick"而不是DOM 中的click。
在IE中使用attachEvent()方法与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级时,事件处理程序的作用域是所属元素的作用域。而attachEvent()方法,事件处理程序的作用域是在全局作用域下运行。也就是说this等于window.
var btn = document.getElementById('mybtn');
btn.attachEvent("onclick",function () {
alert(this === window);//true
});
与addEventListner()方法一样,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。
var btn = document.getElementById('mybtn');btn.attachEvent("onclick",function () {
alert("clicked");
});
btn.attachEvent("onclick",function () {
alert("hello world!");
});
这里调用了两次attachEvent(),为同一个按钮添加了两个不同的事件处理程序。不过与DOM方法不同的是,这些事件处理程序不同以添加他们的顺序而执行,而是以相反的顺序执行。单击这个按钮,首先看到的是“hello world"再看到的是”clicked“。
使用attachEvent()添加事件可以通过detachEvent()清除。不过,只要能够将对相同函数的引用传给detachEvent(),就可以移除相同的事件处理程序。
跨浏览器事件处理程序
为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的javascript库,还有一些开发人员会自己开发最合适的事件处理的方法。要保证处理事件的代码能够在大多浏览器下一致的运行,只需要关注冒泡阶段。
第一个创建的方法是addHander(),它的职责是视情况分别使用DOM0级方法,DOM2级方法或IE来添加事件。接收三个参数(要操作的元素,事件名称和事件处理程序函数)。
var EventUtil = {
addHander: function (element, type, handler){
if (element.addEventListener){
element.addEventListner(type,handler,false);
}else if(element.attachEvent)
{ element.attachEvent("on" + type,handler);
}else{
element["on"+type] = handler;
}
},
removeHandler: function(element,type,handler){
if (element.removeEventListner){
element.removeEventListner(type,handler,false);
}else if(element.detachEvent){
element.detachEvent("on" + type,handler);
}else{
element["on" + type] = null;
}
}
};
事件对象
在触发DOM 上的某个事件时,会产生一个事件对象event,这个对象中包含所有与事件有关的信息。包括导致事件的元素,事件类型以及其他与特定事件相关的信息。所有的浏览器都支持event对象,但支持方式不同。
DOM 中的事件对象
兼容DOM 的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。
var btn = document.getElementById("mybtn");
btn.onclick = function () {
alert(event.type);//click
}
btn.addEventListner("click",function () {
alert(event.type);//click
},false);
event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下面的成员。
1.bubbles Boolean 只读 表面事件是否冒泡
2.cancellable Boolean 只读 表明是否取消事件的默认行为
3.currentTarget Element 只读 其事件处理程序当前正在处理事件的哪个元素
4.defaultPrevented Boolean 只读 为true表示已经调用了preventDefault()
5.detail Integer 只读 与事件相关的细节信息
6.eventPhase Integer 只读 调用事件处理程序的阶段:1表示捕获阶段,2表示处于目标阶段,3表示处于冒泡阶段
7.preventDefault() Function 只读 取消事件默认行为。如果cancelable是true则可使用这个方法
8.stopPropagation() Function 只读 取消事件的进一步冒泡或捕获
9target Element 只读 事件目标
10.trusted Boolean 只读 为true表示事件是浏览器生成的。为false表示事件是由开发人员通过JS创建的
11.type String 只读 被触发事件的类型
12.view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的window对象。
在事件处理程序内部,对象this 始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了某个特定元素,则this,currentTarget和target包含相同的值。
跨浏览器事件对象
var EventUtil = {
//跨浏览器事件绑定
addHandler: function (element,type,halder){
if(element.addEventListener){
element.addEventListener(type,halder,false);
}else if(element.attachEvent){
element.attachEvent("on" + type, halder);
}else{
element["on"+type]= halder;
}
},//快浏览器事件移除
removeHandler: function(element,type,halder){
if(element.removeEventListener){
element.removeEventListener(type,halder);
}else if(element.detachEvent){
element.detachEvent("on"+type,halder);
}else{
element["on" + type]= null;
}
},//跨浏览器事件对象
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;
}
},
stopPropagation:function(event){
if(event.stopPropagation){
event.stopPropagation();
}else {
event.cancelBubble = true;
}
}
};
内存和性能
由于事件处理程序可以为现代Web应用程序提供交互能力,因此许多开发人员会不分青红皂白的向页面添加大量的处理程序。在创建GUI的语言(C#)中,为GUI中的每个按钮添加一个onclick事件处理程序司空见惯的事,而且这样做也不会导致什么问题。可是在javasc中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM 访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的。
事件委托
对”事件处理程序过多“问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如,click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。
<ul id="myLinks">
<li id="goSomeWhere">Go SomeWhere</li>
<li id="doSomething">Do Something</li>
<li id="sayHi">Say Hi</li>
</ul>
其中包含3个被单击后会执行操作的列表项。按照传统的做法,需要像下面这样为它们添加三个事件处理程序
var item1 = document.getElementById("goSomeWhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("SayHi");
EventUtil.addHandler(item1,"click",function (event){
//内容省略
});
EventUtil.addHandler(item2,"click",function (event){
//
});
EventUtil.addHandler(item3,"click",function (event){
//
});
如果在一个复杂的Web应用中,对所有可单击的元素都采用这种方式,那么结果就会有数不清的代码用于添加事件处理程序。此时可以用事件委托技术解决这个问题,使用事件委托,只需要在DOM树种尽量最高的层次上添加一个事件处理程序,例如
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
//内容省略
break;
case "goSomeWhere":
//
break;
case "sayHi":
//
break;
}
});
在这段代码里,我们所使用事件委托只为<ul>元素元素添加了一个onclick事件处理程序。由于所有列表项都是这个元素的子节点,而且他们的事件会冒泡,所以单击事件最终会被这个函数处理。我们知道,事件目标是被单击的列表项,故而可以通过检测id属性来决定采取适当的操作。与前面未使用事件委托的代码比一比,会发现这段代码的消耗更低。因为只取得了一个DOM元素,只添加了一个事件处理程序。虽然对用户来说最终的结果相同,但这种技术需要占用的内存更少。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。
移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的javascript代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托的方法减少这种连接数量。除此之外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中那些留有过时不用的”空事件处理程序“,也是造成web应用程序内存与性能问题的主要原因。
在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。这可能是通过纯粹的DOM操作,例如使用removChild()和replaceChild()方法,但更多地是发生在使用innerHtml替换页面中一部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。
<div id ="muDiv">
<input type="button" value="Click Me" id="mybtn">
</div>
<script>
var btn = document.getElementById("mybtn");
btn.onclick = function (){
document.getElementById("myDiv").innerHTML = "processing...";
}
</script>
这里有一个按钮包含在DIV中,为避免双击,单击这个按钮时就将按钮移除并替换成一条消息;但问题在于,当按钮被从页面中移除时,它还带着一条事件处理程序。div上设置了innerHTML可以把按钮移走,但是事件处理程序还是和按钮保持着联系,有的浏览器在这种情况下不会做出恰当的处理,它们很有可能会将对元素和对事件处理程序的引用都保存在内存中。如果你知道这个元素要被移除最好事先自己移除事件处理程序。