JavaScript进阶:深入理解事件对象
事件流
事件流描述的是从页面中接收事件的顺序。对于事件流IE和Netscape开发团队提出了相反的事件流概念。前者提出的是事件流冒泡(也是现在的主要事件流),后者提出了事件流捕获。
事件冒泡
IE的事件流叫做事件冒泡,开始时由最具体的节点接收事件,向上逐级传播到最不具体的节点(document)。比如说:
如果点击div,那么这个点击事件就会如下顺序传播:
div>body>html>document。
现代所有浏览器都支持事件冒泡,但是具体实现上还是由一点差别。IE5.5及更早版本中的事件会跳过html(从body直接到document)。IE9、Firefox、Chrome和Safari则将事件一直冒泡到window。
事件捕获
Netscape团队提出的另一种事件流时事件捕获。事件捕获的思想是从最不具体的节点开始接收事件,到最具体的节点,思想与事件冒泡恰恰相反。事件捕获的目的或者说用意是在事件到达预定目标节点之前捕获它。
仍以事件冒泡中的代码作为例子,单击div,事件会以如下顺序传播:document>html>body>div。
事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。首先发生的是事件捕获,为截获事件提供机会。然后,是捕获目标,实际的目标接到事件。最后,目标可以在冒泡阶段响应事件。
在DOM事件流中,实际的目标(div元素)在捕获阶段不会接收到事件,这意味着在捕获阶段,从document到body就停止了。在处于目标阶段时,事件相应(在事件处理中处于目标阶段会被看成冒泡阶段的一部分)。最后到冒泡阶段事件传回文档。
依旧以上述事件冒泡中例子为例:点击div之后,首先进入捕获阶段,从document>html>body,然后进入处于目标阶段,截获到目标div节点,最后进入冒泡阶段,从div>body>html>document逐级传播事件。
要注意一点,多数支持DOM事件流的浏览器都实现了一种特定的行为:即使“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Firefox、Chrome、Safari和opera9.5及更高版本都会在捕获阶段触发对象上的事件。结果,就是由两个机会在目标对象上操作事件。IE9、Firefox、Chrome、Safari和opera都支持DOM事件流;IE8及更早版本不支持DOM事件流。
事件处理程序
IE事件和DOM事件在处理程序上是不同的。
1、DOM0级事件处理程序
每个元素都有自己事件处理事件属性,例如btn的属性onclick。DOM0级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在它依附的元素的所在作用域中运行的。将事件处理程序属性的值设置为函数,就可以指定事件处理程序。比如:
var btn =document.getElementById('oDiv');
btn.onclick = function () {
alert(this.id);
}
单击div显示它的id。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。也可以删除这种事件处理程序,将事件处理程序的属性设置为null,即btn.onclick = null。
2、DOM2级事件处理程序
DOM2级事件处理程序定义了两个方法,用于处理和删除事件处理程序的操作:addEventListener()和removeEventListener()。
所有的DOM节点都支持这两种方法,且它们包含三个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。如果最后的参数布尔值为true,表示在捕获阶段调用事件处理程序,如果最后的参数布尔值为false,则表示在冒泡阶段调用事件处理程序,一般设置为false,因为可以最大限度兼容各种浏览器。
与DOM0级方法一样,使用这两种方法添加的事件处理程序也是在其所依附元素的作用域内。
比如:
如果为一个节点添加多个事件处理程序,那么在事件触发时,多个事件处理程序按序执行。比如:
var btn =document.getElementById('btn');
btn.addEventListener("click", function(){
alert("hello");
}, false);
btn.addEventListener("click", function () {
alert("world");
}, false); //现出现hello,然后是world
通过addEventListenner(),,添加的事件处理程序只能被removeEventListener删除,移除时传入的第二个参数即事件处理程序的函数,必须与addEventListenner()相同。必须要注意的是通过addEventListenner()添加的匿名函数无法移除。比如:
var btn =document.getElementById('btn');
btn.addEventListener("click", function(){
alert("hello");
}, false);
btn.removeEventListener("click", function(){
alert("hello");
}, false); //没有用,会默认为是两个不同的函数
正确的写法是:
var btn =document.getElementById('btn');
function foo (){
alert("hello");
}
btn.addEventListener("click", foo , false);
btn.removeEventListener("click", foo , false); //移除成功,第二个参数都是foo函数。
3、IE事件处理程序
IE事件定义了处理事件处理程序的两个方法:attachEvent()和removeEvent()。这两个方法有两个参数:事件处理程序的名称和事件处理程序的函数。由于IE8及之前的版本都只支持事件冒泡,是通过attachEvent()添加的事件处理程序只能在冒泡阶段被调用。
IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法和DOM2级方法时,事件处理程序会在它所依附的元素的所在作用于运行,而使用attachEvent()方法则是在全局作用域运行,因此this指向window。
比如:
var btn =document.getElementById('btn');
btn.attachEvent("onclick", function () {
alert(this === window); //true
})
为一个元素添加多个事件处理程序时,触发的顺序与DOM方法的顺序相反,顺序为后添加先触发。
跨浏览器的事件处理程序
兼容DOM和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.removeEvent){
element.removeEvent("on" + type, handler);
}else {
element["on"+type] = null;
}
}
}
事件对象
1、DOM事件对象
在触发DOM上的某个事件时,会产生一个事件对象event。这个对象中包含着所有与事件有关的信息。包括导致事件的元素,事件的类型以及其他与特定事件相关的信息。
比如:
鼠标操作导致的事件对象中,会包含鼠标位置的信息,
键盘操作导致的事件对象中,会包含按下的键有关的信息,
所有浏览器都支持event对象,event对象会传入DOM0级,DOM2级,HTML指定,的事件处理程序中,但支持的方式不同,所以也会涉及跨浏览器的部分。例子如下
function handler(){
alert(event.type);
};
EventUtil.addHandler(btn,'click',handler);//接上篇笔记
event:在支持至少DOM2级的浏览器内,无论使用html特性指定,dom0级,2级,该对象都是该事件内置对象,可以在事件处理函数内直接使用。
this:在支持至少DOM2级的浏览器内,无论使用html特性指定,dom0级,2级,都是指向当前正在处理事件的那个元素。
事件对象的属性和方法如下表:
currentTarget:this对象始终等于他的值,随着事件冒泡或者捕获,他得值等于捕获或冒泡到的上级元素的值。target:只包含事件的实际目标。比如
var btn =document.getElementById('btn');
btn.addEventListener("click", function(){
alert(event.currentTarget === this); //true
alert(event.target === this); //true
}, false);
type:可以利用type属性为一个元素同时添加多类事件处理程序。比如:
var btn =document.getElementById('btn');
function handle () {
switch(event.type){
case "click": alert('click');break;
case "mouseout": alert('mouseout');break;
default:break;
}
}
btn.onclick = handle;
btn.onmouseout = handle;
cancelable、preventDefault():只有cancelable为true时,才可以使用preventDefault()方法,来取消其默认行为。
var oDiv = document.getElementById('oDiv');
var btn =document.getElementById('btn');
btn.onclick = function () {
alert(this.id);
event.stopPropagation();
}
eventPahse:用来确定事件当前位于事件流的哪个阶段。例子如下:
var oDiv = document.getElementById('oDiv');
var btn =document.getElementById('btn');
function handle () {
alert(event.eventPhase);
event.stopPropagation();
}
btn.addEventListener("click", handle, false);
oDiv.addEventListener("click", handle, true);
oDiv.addEventListener("click", handle, false);
IE事件对象
跨浏览器的事件对象
虽然DOM事件和IE事件中event对象不同,但他们依然有很多相似性。可使用如下代码兼容不同的浏览器:
var EventUtil = {
getEvent : function(event){
return event ? event : window.event;
}
getTarget : function (event) {
return event.target ? event.target : event.srcElement;
}
preventDefault : function (event) {
if(preventDefault){
event.preventDefault();
}else {
event.returnValue = false;
}
}
stopPropagation : function(event){
if(event.stopPropagation){
event.stopPropagation();
}
else {
event.cancelable = true;
}
}
}