# 事件
JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。
可以使用侦听器(或处理程序)来预订事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式的模型, 让页面的结构, 样式, 行为之间解耦
# 事件流
『 事件流 』描述的是从页面中接收事件的顺序。如果你单击了某个按钮,他们都认为单击事件不仅仅发生在按钮上。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。
- IE的事件流是事件冒泡流
- Netscape Communicator的事件流是事件捕获流。
# 事件冒泡
IE的事件流叫做事件冒泡(event bubbling)
事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)
# 事件捕获
Netscape Communicator团队提出的另一种事件流叫做事件捕获(event capturing)。
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。
事件捕获的用意在于在事件到达预定目标之前捕获它。
# DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段, 处于目标阶段, 事件冒泡阶段。
首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
在DOM事件流中,实际的目标(<div>
元素)在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到<html>
再到<body>
后就停止了。下一个阶段是“处于目标”阶段,于是事件在<div>
上发生,并在事件处理(后面将会讨论这个概念)中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。
# 事件处理程序
事件就是用户或浏览器自身执行的某种动作。诸如click、load 和 mouseover,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)
事件处理程序的名字以"on"开头,因此 click 事件的事件处理程序就是 onclick
,
# HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的 JavaScript 代码
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()" />
# DOM0级事件处理程序
首先必须取得一个要操作的对象的引用。每个元素(包括 window
和 document
)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
}
使用DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的 this
引用当前元素。
# DOM2级事件处理程序
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()
和removeEventListener()
。
所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数 和 一个布尔值。
最后这个布尔值参数如果是true
,表示在捕获阶段调用事件处理程序;如果是false
,表示在冒泡阶段调用事件处理程序。
添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
通过addEventListener()
添加的事件处理程序只能使用removeEventListener()
来移除;移除时传入的参数与添加处理程序时使用的参数相同。
这也意味着通过addEventListener()
添加的匿名函数将无法移除.
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//这里省略了其他代码
btn.removeEventListener("click", function(){ //没有用!
alert(this.id);
}, false);
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
# IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()
和detachEvent()
。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()
添加的事件处理程序都会被添加到冒泡阶段。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
注意,attachEvent()
的第一个参数是"onclick"
,而非DOM的addEventListener()
方法中的"click"
。
在IE中使用attachEvent()
与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()
方法的情况下,事件处理程序会在全局作用域中运行
# 事件对象
在触发DOM上的某个事件时,会产生一个事件对象event
,这个对象中包含着所有与事件有关的信息。
兼容DOM的浏览器会将一个event
对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event
对象。
在事件处理程序内部,对象this
始终等于currentTarget
的值(注册了事件处理程序的那个元素),而target
则只包含事件的实际目标。
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
当单击这个例子中的按钮时,this
和currentTarget
都等于document.body
,因为事件处理程序是注册到这个元素上的。然而,target
元素却等于按钮元素,因为它是click
事件真正的目标。由于按钮上并没有注册事件处理程序,结果click
事件就冒泡到了document.body
,在那里事件才得到了处理。
要阻止特定事件的默认行为,可以使用preventDefault()
方法。例如,链接的默认行为就是在被单击时会导航到其href
特性指定的URL。但只有cancelable
属性设置为true
的事件,才可以使用preventDefault()
来取消其默认行为。
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
stopPropagation()
方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。
事件对象的eventPhase
属性,可以用来确定事件当前正位于事件流的哪个阶段。
- 如果是在捕获阶段调用的事件处理程序,那么
eventPhase
等于1
; - 如果事件处理程序处于目标对象上,则
eventPhase
等于2
; - 如果是在冒泡阶段调用的事件处理程序,
eventPhase
等于3
。 这里要注意的是,尽管“处于目标”发生在冒泡阶段,但eventPhase
仍然一直等于2。
# 事件类型
直接看文档就好了!
# 内存和性能
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
# 事件委托
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。使用事件委托,只需在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>
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;
}
});