事件流
所谓的事件流,就是指从页面接收事件的顺序。所谓的事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。
事件冒泡
IE中的事件流叫做事件冒泡,即事件开始时是由最具体的元素接收,然后逐级向上传播到较为不具体的节点。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件流</title>
</head>
<body>
<div id='myDiv'>点击</div>
</body>
</html>
当点击上面代码的div元素时,事件的传播顺序如下图所示:
在事件冒泡过程中,click事件首先是发生在div元素上的,然后才逐级向起父层传播。
事件捕获
Netscape的事件流是事件捕获。事件捕获的思想是:事件是从不太具体的节点传播到具体的节点的。
仍以上面的html页面作为例子,事件捕获中,事件的传播顺序如下图所示:
在事件捕获过程中,document对象最先接收到click事件,然后事件沿DOM树依次向下,直到传播到事件的实际目标为止,这里的目标即div元素。
DOM事件流
‘DOM2级事件’的事件流有三个阶段,分别为:事件捕获阶段、处于目标阶段和事件冒泡阶段。
同样以前面的html页面为例,DOM2级事件流的传播顺序如下图所示:
注意,在捕获阶段,实际目标(在这个例子中,即div元素)是不会接收事件的。在处于目标阶段事件被接收,并且在事件处理中被看成是冒泡阶段的一部分。
事件处理程序
响应某个事件的函数,就叫做事件处理程序(或事件侦听器)。
HTML事件处理程序
HTML事件处理程序就是指与每种事件同名的HTML特性。而这里的特性值应该是能够执行的javascript代码。
如,click事件,作为事件处理程序,在html中可以这样表示:
<input type="button" value="Click Me" οnclick="alert('Clicked')" />
注意,作为特性值的javascript代码,不能使用未经转义的HTML语法字符,如和号(&)、双引号(“”)、小于号(<)、大于号(>)等。
还可以像下面这样指定html事件处理程序:
<script type="text/javascript">
function showMessage(){
alert('Hello world!');
}
</script>
<input type="button" value="Click Me" οnclick="showMessage()" />
DOM0级事件处理程序
DOM0级的事件处理程序就是将一个函数赋给一个事件处理程序属性。在这里,事件处理程序被认为是元素的方法,通常全部小写。并且,此时事件处理程序是在元素的作用域中运行的,即程序中this引用当前的元素。
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.οnclick=function(){
alert(this.id); //myBtn
};
</script>
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
也可以删除通过DOM0级方法指定的事件处理程序,只要将他们的值设置为null即可:
btn.οnclick=null; //删除事件处理程序
删除事件处理程序后,再单机按钮将不会有任何动作发生。
DOM2级事件处理程序
DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序,它们分别为:addEventListener()和removeEventListener()。
这两个方法都接受三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。
最后一个布尔值参数,如果是true,则表示在捕获阶段调用事件处理程序;如果是false,则表示在冒泡阶段调用事件处理程序。
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.addEventListener('click',function(){
alert(this.id); //myBtn
},false)
</script>
试用DOM2级方法,可以为元素添加多个事件处理程序,它们是按照添加的顺序来一次执行的:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.addEventListener('click',function(){
alert(this.id); //myBtn
},false);
btn.addEventListener('click',function(){
alert('Hello world!'); //Hello world!
},false);
</script>
上面这段代码,会依次弹窗两个弹窗,第一个显示’myBtn‘,第二个显示’Hello world!'.
通过addEventListener()添加的事件处理程序只能通过removeEventListener()来删除,并且,移除时传人的参数必须和添加处理程序时传人的参数相同,否则将无法移除。
注意:addEventListener()添加的匿名函数将无法被移除。即上面的两个事件处理程序是无法被移除的。
因此,可以在外部定义函数,然后再将其传人事件处理程序中,这样的就可以在删除事件处理程序中传人一样的函数了:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
var handler=function(){
alert(this.id);
}
btn.addEventListener('click',handler,false);
btn.removeEventListener('click',handler,false);
</script>
像这样一段代码,点击按钮是不会产生任何效果的。因为事件处理程序被成功移除了。
IE事件处理程序
IE也有两个方法,用于添加和删除事件处理程序,分别为:attachEvent()和detachEvent()。
这两个方法也接受两个相同的参数:事件处理程序名称和事件处理程序函数。由attachEvent()添加的事件处理程序会被添加到冒泡阶段。
注意:attachEvent()的第一个参数与addEventListener()的第一个参数是不同的,会在前面多一个'on‘:
var btn=document.getElementById('myBtn');
btn.attachEvent('onclick',function(){
alert('Clicked!');
});
在使用attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this是等window,而不是元素:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.attachEvent('onclick',function(){
alert(this===window); //true
});
</script>
与addEventListener()一样,attachEvent()也可以为一个元素添加多个事件处理程序,但是,与addEventListener()不同的是,用attachEvent()添加的事件处理程序执行的顺序是相反的,即最后添加的会最先执行:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.attachEvent('onclick',function(){
alert('clicked!');
});
btn.attachEvent('onclick',function(){
alert('Hello!');
});
</script>
上面这段代码,依次弹出的是:’Hello!‘、’clicked!‘。
使用attachEvent()添加的事件可以用detachEvent()来移除,并且传人的参数也要是相同的:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
var btn=document.getElementById('myBtn');
var handler=function(){
alert('clicked!');
};
btn.attachEvent('onclick',handler);
btn.detachEvent('onclick',handler);
</script>
上面这段代码,点击按钮,将不会有任何反应,因为事件已被移除。
跨浏览器的事件处理程序
通过上面的介绍,可以知道,要保证处理事件的代码在大多数浏览器下一致运行,只需要关注冒泡阶段即可,因此可以视情况分别使用DOM0级方法、DOM2级方法或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(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null;
}
}
};
利用上面的对象,我们一起来看一个跨浏览器添加事件处理程序的例子:
<input type="button" value="Click Me" id="myBtn" />
<script type="text/javascript">
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(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null;
}
}
};
var btn=document.getElementById('myBtn');
var handler=function(){
alert('Hello world!');
}
EventUtil.addHandler(btn,'click',handler); //Hello world!
EventUtil.removeHandler(btn,'click',handler);
</script>
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含这所有与事件有关的信息。
所有浏览器都支持event对象,但是支持的方式不同。
HTML事件处理程序中的事件对象
HTML事件处理程序会创建一个封装着元素属性值的函数,该函数有一个局部变量event,也就是事件对象。
通过event变量,可以直接访问事件对象,this值等于事件的目标元素。
<input type='button' value='click me' οnclick='alert(this.value)' />
这个动态创建的函数,还可以像访问局部变量一样访问document及该元素本身的成员:
<input type='button' value='click me' οnclick='alert(value)' />
如果当前元素是一个表单输入元素,则作用域中还会包含访问表单元素(父元素)的入口:
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="Echo Username" οnclick="alert(username.value)">
</form>
这里,直接引用了username元素,返回了它的值。
在HTML中指定事件处理程序有两个缺点:
- 存在时差问题。用户可能会在HTML元素一出现在页面上就触发相应的事件,这是要是处理程序尚不具备执行条件,将会保存。所以,将HTML事件处理程序封装在try-catch块中,是经常的作法。
- 这样扩展事件处理程序的作用域在不同浏览器中会导致不同的结果。
因此,不建议使用HTML事件处理程序,而应该用javascript指定事件处理程序。
DOM中的事件对象
无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传人event对象。它包含与创建它的特性事件有关的属性和方法。
触发的事件类型不一样,可用的属性和方法也不一样,不过,所有事件都会有下表列出的成员:
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
bubbles | Boolean | 只读 | 表明事件是否冒泡 |
cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 |
defaultPrevented | Boolean | 只读 | 为true表示已经调用了preventDefault() |
detail | Integer | 只读 | 与事件相关的细节信息 |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”阶段,3表示冒泡阶段 |
preventDefault() | Function | 只读 | 取消事件的默认行为。如果cancelable是true,则可以使用这个方法 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用 |
stopPropagation() | Function | 只读 | 取消事件进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
target | Element | 只读 | 事件的目标 |
trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示事件是有开发人员通过javascript创建的 |
type | String | 只读 | 被触发的事件的类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。
注意:只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁。
IE中的事件对象
与访问DOM中的event对象不同,要访问IE中的event对象有几种不通过的方法,取决于指定事件处理程序的方法。
在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。
如果事件处理程序是使用attachEent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中。
//DOM0级方法
<a href="#" id="myBtn">click me</a>
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.οnclick=function(){
var event=window.event;
alert(event.type); //click
}
</script>
//attachEent()方法
<a href="#" id="myBtn">click me</a>
<script type="text/javascript">
var btn=document.getElementById('myBtn');
btn.attachEvent('onclick',function(event){
alert(event.type); //click
});
</script>
IE的event对象同样也包含与创建他的事件相关的属性和方法,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都会包含下列属性和方法:
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
cancelBubble | Boolean | 读/写 | 默认值是false,但将其设置为true就可以取消事件冒泡(与DOM中的stopPropagation()方法的作用相同) |
returnValue | Boolean | 读/写 | 默认值为true,但将其设置为false就可以取消事件的默认行为(与DOM中的preventDefault()方法的作用相同) |
srcElement | Element | 只读 | 事件的目标(与DOM中的target属性相同) |
type | String | 只读 | 被触发事件的类型 |
跨浏览器的事件对象
虽然DOM和IE中的event对象不同,但是IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样,所以还是可以实现跨浏览器获得事件对象。
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(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,handler);
}else{
element['on'+type]=null;
}
},
//获得event对象
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(evemt.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
}
};
除了前面添加的添加事件以及移除事件的方法外,这里还新添加了四个方法,分别为getEvent()、getTarget()、preventDefault()和stopPropagation()。
在使用这些方法时,必须假设有一个事件对象传人到事件处理程序中,而且要把该变量传给这个方法:
btn.οnclick=function(event){
<strong>event=EventUtil.getEvent(event);</strong>
}
获得事件目标:
btn.οnclick=function(event){
event=EventUtil.getEvent(event);
<strong>var target=EventUtil.getTarget(event);</strong>
}
取消默认行为:
btn.οnclick=function(event){
event=EventUtil.getEvent(event);
<strong>EventUtil.preventDefault(event);</strong>
}
阻止冒泡跟取消默认行为一样,直接调用EventUtil对象的stopPropagation()方法。