javascript学习笔记——事件流、事件处理程序、事件对象、事件类型、内存和性能
1.事件流
事件流描述的是从页面中接收事件的顺序;
1)事件冒泡
IE的事件流叫事件冒泡;即,事件开始时右具体的元素接收,然后逐级向上传播到较为不具体的节点;
所有现代的浏览器都支持事件冒泡,但在具体实现上还是有一些差别;
有HTML页面如下:
<!DOCTYPE html>
<html>
<head>
<title>ebevent bubbing example</title>
</head>
<body>
<div id="myDir">click me</div>
</body>
</html>
如果在此页面中点击div,则事件发生顺序如下图所示:click事件沿着DOM树向上传播,在每一级够会发生,直至传播到document对象;
2)事件捕获
事件捕获的是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收事件。事件捕获的用意是在事件达到预期目标之前捕获它。
由于老版本的浏览器不支持,因此很少人使用事件捕获。
以上面的HTML页面为例,事件捕获的顺序:document对象首先接到click事件,然后事件沿着DOM树依次向下传播到事件的实际目标;
3)DOM事件流
DOM2事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
还是以上面的HTML页面作为例子:
2.事件处理程序
事件:就是用户或浏览器自身执行的某种动作。
响应某个事件的函数就叫做事件处理程序;
事件处理程序的名字以“on”开头;
事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码
1)HTML事件处理程序
该事件处理程序特征就是在HTML中有一个与相应事件处理程序同名的特性,比如"onclick"
该处理程序会创建一个封装着元素属性值的函数,这个函数中有一个局部变量event,也就是事件对象;通过event变量,可以直接访问事件对象;
<input type="button" value="click me" onclick="alert(event.type)">
<script type="text/javascript">
function showMessage(){
alert("hello world");
}
</script>
<input type="button" value="click me" onclick="showMessage()">
HTML事件处理程序的缺点:
①存在一个时差问题。因为用户可能会在HTML元素一出现在页面就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件;这就有可能引发错误,因此,很多HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面。
<input type="button" value="click me" onclick="try{showMessage();}catch(ex){}">
②扩展事件处理程序的作用域链在不同浏览器中会导致不同的结果。不同的javascript引擎遵循的标识解析规则略有差异,很可能会在访问非限定对象成员时出错。
③HTML与JavaScript代码紧密耦合。维护不方便。
2)DOM0级事件处理程序
通过js指定事件处理程序的传统方式:就是将一个函数赋值给一个事件处理程序属性。至今仍为所有现代浏览器支持;
优点:简单;具有跨浏览器的优势。
要使用js指定事件处理程序,首先必须取得一个要操作的对象引用。
每个元素都有自己的事件处理程序属性。这些属性通常全部小写。
var btn=document.getElementById("myBtn");
btn.onclick=function(){
alert(this.id); //"myBtn"
}
btn.onclick=null; //删除事件处理程序
指定事件处理程序的代码在运行以前不会指定事件处理程序,因此,这时候如果这些代码位于按钮后面,就有可能在一段时间内怎么单击都没反应。
DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素作用域中运行。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理;
3)DOM2级事件处理程序
DOM2级事件定义了两个用于指定和删除事件处理程序的操作:addEventListener(事件名,事件处理程序函数,布尔值),removeEventListener(事件名,事件处理程序函数,布尔值)。所有DOM节点中都包含着两个方法。
第三个参数,如果为true则表示在事件捕获阶段调用事件处理程序;false则表示在冒泡阶段调用事件处理程序。
大多数情况下,将事件处理程序添加到事件冒泡阶段,这样可以最大限度地兼容各种浏览器。
添加的事件处理程序也是在其依附的元素的作用域中运行。
优点:可以添加多个事件处理程序。
var btn=document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert(this.id);
},false);
btn.addEventListener("click",function(){
alert(hello world);
},false);
这两个事件处理程序会按照它们的天际顺序触发,因此首先会显示元素的ID,其次会显示“hello world”消息。
通过addEventListener()天际的事件处理程序只能使用removeEventListener()方法来移除。但是移除的参数和添加事件处理程序的参数一样。但是,如果addEventListener()的第二参数使用的是匿名函数的话,那么在在执行函数传入同样的参数,结果不能达到移除的效果,因为两个匿名函数虽然代码一样,但是代表的是完全不同的函数
能达到移除事件效果的例子:
var btn =document.getElementById("n=myBtn");
var handler=function(){
alert(this.id);
}
//添加
btn.addEventListener("click",handler,false);
//移除
btn.removeEventListener("click",handler,false); //有效的移除事件
4)IE事件处理程序
IE实现了用于指定和删除事件处理程序的两个函数:attachEvent(事件处理程序名称,事件处理程序函数)、detachEvent(事件处理程序名称,事件处理程序函数),添加的事件处理程序都会被添加到冒泡阶段。
第一个参数中的处理事件程序名,比如点击事件,是有带on,“onclick”
IE中使用attacheEvent()与使用DOM0级方法的主要区别在于:事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域运行。在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此,this等于window。
优点:同样可以为一个元素添加多个事件处理程序,但是事件的触发顺序与添加顺序相反。
var btn=document.getElementById("myBtn");
btn.attachEvent("onclick",function(){
alert("hello");
})
btn.attachEvent("onclick",function(){
alert("world");
})
这个例子的执行结果,首先看到的是"world",然后看到的是"hello"
使用attachEvent()添加的事件可以通过detachEvent()来移除。条件是必须使用相同的参数,与DOM方法一样,处理程序函数不能是匿名函数,只要能够将对相同函数的引用传给移除函数,就可以有效的移除相应的事件处理程序。
5)跨浏览器的事件处理程序
var EventUtil={ addHandler:function(element,type,handler){ //指定事件处理程序 if(element.addEventListener){ element.addEventListner(type,handler,false); //DOM2 }else if(element.attachEvent){ element.attachEvent("on"+type,handler); //IE }else{ element["on"+type]=handler; //DOM0 } }, removeHandler:function(element,type,handler){ //移除事件处理程序 if(element.removeEventListener){ element.removeEventListner(type,handler,false); //DOM2 }else if(element.detachEvent){ element.detachEvent("on"+type,handler); //IE }else{ element["on"+type]=null; //DOM0 } } };
3、事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。
所有浏览器都支持event对象,但支持的方式不同。
1)DOM中的事件对象
2)IE中的事件对象
3)跨浏览器的事件对象
var EventUtil={
getEvent:function(event){
return event?event:window.event; //获取event对象的引用
},
getTargrt:function(event){
return event.target||event.sreElement; //返回事件目标
},
preventDefault:function(event){ //取消事件额默认行为
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;
}
},
stopPropagation:function(event){ //阻止事件冒泡
if(event.stopPropagation){
event.stopPropagation;
}else{
event.cancelBubble=true;
}
}
};
4、事件类型
1)UI事件:当用户与页面上的元素交互时触发;
①load事件
当页面完全卸载后,就会触发window上面的load事件。
两种定义方式:
a.通过javascript来指定事件处理程序(建议使用)
b.为<body>元素添加一个onload特性;
//javascript方式 利用之前定义的跨浏览器方式指定处理程序
EventUtil.addHandler(window,"load",function(event){
alert("load");
});
//为body元素添加onload特性
<body onload="alert('load')"></body>
在创建新的<img>元素时,可以为其指定一个事件处理程序,以便图像加载完毕后给出提示。此时最重要的是在要在指定src属性之前先指定事件,
EventUtil.addHandler(window,"load",function(){
var image=docuemnt.createElement(img);
EventUtil.addHandler(image,"load",function(event){
event=EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});
document.body.appendChild(image);
image.src="smile.gif";
});
解析:window指定了onload事件处理程序。原因在于,我们是想向DOM中添加一个新元素,所以必须确定页面已经加载完毕——如果在页面加载前操作document.body会导致错误。 创建了一个新的图像元素,并设置了其load事件处理程序,最后又将这个图像添加到页面中,还设置了它的src属性。注意:新图像元素不一定要从添加到文档后才开始下载,只要设置src属性就会开始下载。
<script>元素也会触发load事件,以便开发人员确定动态加载的javascript文件是否加载完毕。与图像不同,只有在设置了<script>元素的src属性,并将该元素添加到文档后,才会开始下载javascript文件。
②unload事件
这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
两种定义方式:
a.通过javascript来指定事件处理程序
b.为<body>元素添加一个onunload特性;
//javascript方式 利用之前定义的跨浏览器方式指定处理程序
EventUtil.addHandler(window,"unload",function(event){
alert("unload");
});
//为body元素添加onunload特性
<body onunload="alert('unload')"></body>
③resize事件
当浏览器窗口被调整到一个新高度或宽度的时候,就会触发resize事件。同样有两种事件指定方式javascript方式和<body>元素中的特性来指定;还是推荐使用javascript方式:
//javascript方式 利用之前定义的跨浏览器方式指定处理程序
EventUtil.addHandler(window,"resize",function(event){
alert("resize");
});
关于何时会触发resize事件,不同浏览器有不同的机制。
IE、Safari、Chrome和Opera会在浏览窗口变化了1像素时就触发了resize事件,然后随着变化不断重复触发。
Firefox则只会在用户停止调整窗口大小是才会触发resize事件。
注意:不要在这个事件的处理程序中加入大计算量的代码,因为这些代码有可能被频繁执行,从而导致浏览器反应明显变慢。
④scroll事件
虽然scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。
EventUtil.addHandler(window,"scroll",function(event){
if(document.compatMode=="CSS1Compat"){
alert(document.documentElement.scrollTop); //混杂模式
}else{
alert(document.body.scrollTop); //标准模式
}
});
scroll事件会在文档被滚动期间重复被触发,所以尽量保持事件处理程序的代码简单。
2)焦点事件
①blur:在元素失去焦点时触发。这个事件不会冒泡;所有的浏览器都支持它。
②focus:在元素获取焦点时触发。这个事件不会冒泡;所有的浏览器都支持它;
③focusin:在元素获得焦点时触发。冒泡,支持它的有:IE5.5+,Safari5.1+,Opera11.5+和Chrome。
④focusout:在元素失去焦点时触发。支持这个事件的有IE5.5+,Safari5.1+,Opera11.5+和Chrome。
3)鼠标事件
九个鼠标事件如下:
① click:鼠标单击(左键)或按下回车键触发;
② dbclick:鼠标双击(左键)时触发;
③ mousedown:按下任意鼠标按钮时触发,键盘触发不了;
④ mouseenter:鼠标光标从元素外部首次移动到元素范围之内时触发。不冒泡,而且在光标移动到后代元素上不会触发;
⑤ mouseleave:位于元素上方的鼠标光标移动到元素范围之外时触发。不冒泡,而且在光标移动到后代元素上不会触发;
⑥ mousemove:鼠标指针在元素内部移动时重复触发。键盘触发不了;
⑦ mouseout:鼠标指针位于元素上方,然后用户将其移入另一个元素(外部元素或其子元素)时触发。键盘触发不了;
⑧ mouseover:鼠标指针在元素外部,然后用户将其首次移入另一个元素边界之内时触发。键盘触发不了
⑨ mouseup:用户释放鼠标按钮时触发。键盘触发不了;
页面上所有的元素都支持鼠标事件。除了mouseseenter和mouseleave,所有的鼠标事件都会冒泡;
只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件;
只有触发了两次click事件,才会触发一次dbclick事件;
这4个事件的触发顺序如下:
(1)mousedown
(2)mouseup
(3)click
(4)mousedown
(5)mouseup
(6)click
(7)dbclick
click和dbclick事件都会依赖于其他先行事件的触发,而mousedown和mouseuo则不受其他事件影响;
客户区坐标位置
视口中的水平坐标:event.clientX;
视口中的垂直坐标:event.clientY;
所有的浏览器都支持这两个属性;
注意:这两个值不包括页面滚动的距离,因此所代表的位置也不是页面上的位置。
页面坐标位置
pageX和pageY两个属性表示鼠标光标在页面中的位置。
在页面没有滚动的情况下,pageX和pageY的值与clientX和clientY的值相等。
IE8及更早版本不支持事件对象上的页面坐标,但是可以使用客户区坐标和滚动信息可以计算出来。
屏幕坐标位置
screenX和screenY两个属性表示相对于整个电脑屏幕的位置;
鼠标按钮
event对象存在一个button属性,表示按下或释放的按钮。
DOM的button属性又如下三个值:
0:表示主鼠标按钮(左)
1:表示中间的鼠标按钮;
2:表示次鼠标按钮(右键)
4)键盘与文本事件
用户在使用键盘时触发的事件;
keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,就会重复触发;
keypress:当用户按下字符键的时候触发,而且如果按住不放的话,就会重复触发;
keyup:当用户释放键盘上的键时触发;
所有的元素都支持这三个事件,但只有在用户通过文本框输入文本的时候才常用到;
keydown和keypress都是在文本框发生变化之前被触发的,而keyup事件则是在文本框已经发生变化之后被触发的。
5、内存和性能
在javascript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。
原因:
1)每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。
2)必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间;
事件委托
事件委托能够解决“事件处理程序过多”的问题。
事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
使用事件委托只需在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":
document.title="i changed title";
break;
case "gosomething":
document.href="http://www.baidu.com";
break;
case "sayhi":
alert("hi");
break;
}
});
与未使用事件委托,一个一个子元素指定事件处理程序相比,会发现这段代码的事前消耗更低,。虽然对用户来说结果相同,但是这种技术需要占用的内存更少。
适合采用事件委托技术的事件包括:click,mousedown,mouseup,keydown,keyup和keypress。
虽然mouseover和mouseout事件也冒泡,但要适当处理它们并不容易,而且经常需要计算元素的位置
移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的javascript代码之间就会建立一个连接。这种连接越多,页面执行就会越慢;
解决:
采用事件委托技术,限制建立的连接数量。
在不需要的时候移除事件处理程序;
内存中留有那些过时不用的'空事件处理程序”,是造成web应用程序内存与性能问题的主要原因;
两种情况造成"空事件处理程序":
1)从文档中移除带有事件处理程序的元素时。
在使用InnerHTML替换页面中的某一部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当做垃圾回收。
如果知道某个元素即将被移除,那么最好手工移除事件处理程序;
<div id="myDir"> <input type="button" value="click me" id="myBtn"> </div> <script type="text/javascript"> var btn=document.getElementById("myBtn"); btn.onclick=function(){ //.... btn.onclick=null; //移除事件处理程序 document.getElementById("myDir").innerHTML="processing"; } </script>
设置innerHTML属性之前,先移除了按钮的事件处理程序。这样就确保了内存可以被再次利用,而从DOM中移除按钮也做到干净利索。
注意:在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的先决条件;
2)卸载页面的时候;
如果在页面被卸载之前没有清理干净事件处理程序,那它会滞留在内存中。
一般来说,最好的做法就是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。
上一篇: html5之 type="number" 博客分类: J2ee
下一篇: js(十四)---事件机制
推荐阅读
-
javascript学习笔记——事件流、事件处理程序、事件对象、事件类型、内存和性能
-
详解JavaScript中的事件流和事件处理程序
-
JavaScript 事件流、事件处理程序及事件对象总结
-
详解JavaScript中的事件流和事件处理程序
-
JavaScript 事件流、事件处理程序及事件对象总结
-
学习JavaScript事件流和事件处理程序_javascript技巧
-
JavaScript学习笔记(5)事件处理之事件流与事件处理函数分配
-
学习JavaScript事件流和事件处理程序_javascript技巧
-
JavaScript事件学习之事件流、处理程序和对象总结
-
详解解读JavaScript中的事件流和事件处理程序(图文教程)