javascript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。
IE的事件流叫做事件冒泡(event bubbling),即事件开始由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
事件冒泡过程:
如果你单击了div元素,click事件首先在div元素上发生,然后click事件沿DOM树向上传播,在每一级节点都会发生,直至传播到document对象。
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
事件捕获过程:
事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM树依次向下,一直传播到事件的实际目标div。
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
DOM2事件流过程:
在DOM事件流中,实际目标div在捕获阶段不会接收到事件,这意味着在捕获阶段,事件从document到html再到body后就停止了。下一阶段是“处于目标”阶段,于是事件在div上发生,并在事件处理中被看成冒泡阶段的一部分。然后冒泡发生,事件又传播回文档。
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。
DOM中的事件对象:
兼容DOM的浏览器会将一个event对象传入到事件处理程序中。
event对象包含与创建它的特定事件有关的属性与方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。
如何通过一个函数处理多个事件,使用type属性。
var btn = document.getElementById("myBtn");
var handler = function(event){
switch(event.type){
case "click":
alert("clicked);
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = ""
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
IE中的事件对象:
与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。
与DOM的event对象一样,所有事件对象会包含下表所列的属性和方法。
在javascript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致DOM访问次数,会延迟整个页面的交互就绪时间。
从如何利用好事件处理程序的角度出发,来寻找一些提升性能的方法。
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
以下面代码为例:
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
其中包含3个被单击后会执行操作的列表项。按照传统做法,需要为它们分别添加3个事件处理程序。
使用事件委托,只需要在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 the document‘s title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi":
alert("hi);
break;
}
});
在这段代码里,我们使用事件委托只为ul元素添加了一个onclick事件处理程序。由于所有列表项都是这个元素的子节点,而且它们的事件会冒泡。所以单击事件最终会被这个函数处理。事件目标是被单击的列表项,故而可以通过检测id属性来决定采取适当操作。
如果可以的话,也可以考虑为document对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。
最适合采用事件委托技术的事件包括:click、mousedown、mouseup、keydown、keyup和keypress。
内存中留有那些过时不用的“空事件处理程序”(dangling event handler),也是造成Web应用程序内存与性能问题的主要原因。
先来看一段代码:
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
//先执行某些操作
console.log("我要被替换了!");
document.getElementById("myDiv").innerHTML = "Processing...";
</script>
这里,有一个按钮被包含在div元素中。为避免双击,单击这个按钮时就将按钮移除并替换成一条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被从页面中移除时,它还带着一个事件处理程序呢。在div元素上设置innerHTML可以把按钮移走,但事件处理程序仍然与按钮保持着引用关系。如果你知道某个元素即将被移除,那么最好手工移除事件处理程序。如下:
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
//先执行某些操作
console.log("请先移除myBtn的引用关系!");
//移除事件处理程序
btn.onclick = null;
//移除页面按钮,替换为文本
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
在此,我们设置div的innerHTML属性之前,先移除了按钮的事件处理程序。这样就确保了内存可以被再次利用,而从DOM中移除按钮也做到了干净利索。
注意:在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。