跨浏览器的事件处理程序
要保证处理事件的代码能在大多数浏览器下一致地运行,只需关注冒泡阶段。
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;
}
}
};
这两个方法首先都会检测传入的元素中是否存在DOM2级方法。如果存在DOM2级方法,则使用该方法:传入事件类型、事件处理程序函数和第三个参数false(表示冒泡阶段)。如果存在的是IE的方法,则采用第二种方案。注意,为了在IE8及更早版本中运行,此时的事件类型必须加上“on”前缀。最后一种可能就是使用DOM0级方法(在现代浏览器中,应该不会执行这里的代码)。此时,我们使用的是方括号语法来将属性名指定为事件处理程序,或者将属性设置为null。
可以像下面这样使用EventUtil对象:
var btn = document.getElementById("myBtn");
var handler = function() {
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//这里省略了其他代码
EventUtil.removeHandler(btn, "click", handler);
addHandler()和removeHandler()没有考虑到所有的浏览器问题,例如在IE中的作用域问题。不过,使用它们添加和移除事件处理程序还是足够了。此外还要注意,DOM0级对每个事件只支持一个事件处理程序。好在,只支持DOM0级的浏览器已经没有那么多了,因此这对你而言应该不是什么问题。
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含这所有与事件有关的信息。
DOM中的事件对象
兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0或DOM2),都会传入event对象:
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.type); //"click"
};
btn.addEventListener("click", function(event) {
alert(event.type); //"click"
}, false);
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值:
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.currentTarget === this); //true
alert(event.target === this); //true
};
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,在那里事件才得到了处理。
在需要通过一个函数处理多个事件时,可以使用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;
要阻止特定事件的默认行为,可以使用preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到其href特定指定的URL。如果你想阻止链接导航这一默认行为:
var link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};
只有cancellabel属性设置为true的事件,才可以使用preventDefault()来取消其默认行为。
另外,stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序:
var btn = document.getElementById("link");
btn.onclick = function(event) {
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
alert("Body clicked");
};
对于这个例子而言,如果不调用stopPropagation(),就会在单击按钮时出现两个警告框。可是,由于click事件根本不会传播到document.body,因此就不会触发注册在这个元素上的onclick事件处理程序。
事件对象的eventPhase属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么eventPhase等于1;如果事件处理程序处于目标对象上,等于2;如果是在冒泡阶段调用的事件处理程序,等于3。这里要注意的是,尽管“处于目标”发生在冒泡阶段,但eventPhase仍然一直等于2。
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event) {
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event) {
alert(event.eventPhase); //3
};
只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。
IE中的事件对象
与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
var event = window.event;
alert(event.type); //"click"
};
如果事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中:
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event) {
alert(event.type); //"click"
});
在像这样使用attachEvent()的情况下,也可以通过window对象来访问event对象,就像使用DOM0级方法时。不过为了方便起见,同一个对象也会作为参数传递。
因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为this会始终等于事件目标。故而,最好还是使用event.srcElement比较保险:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick", function(event) {
alert(event.srcElement === this); //false
});
returnValue属性相当于DOM中的preventDefault()方法,它们的作用都是取消给定事件的默认行为。只要将returnValue设置为false,就可以阻止默认行为:
var link = document.getElementById("myLink");
link.onclick = function() {
window.event.returnValue = false;
};
与DOM不同的是,在此没有办法确定事件是否能被取消。
相应地,cancelBubble属性与DOM中的stopPropagation()方法作用相同,都是用来停止事件冒泡的。由于IE不支持事件捕获,因而只能取消事件冒泡;但stopPropagatioin()可以同时取消事件捕获和冒泡:
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function() {
alert("Body clicked");
};
跨浏览器的事件对象
虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来。IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样。不过,这种对应关系让实现两种事件模型之间的映射非常容易。可以对前面介绍的EventUtil对象加以增强:
var EventUtil = {
addHandler: function(element, type, handler) {
//省略的代码
},
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;
}
},
removeHandler: function(element, type, handler) {
//省略的代码
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
以上代码显示,我们为EventUtil添加了4个新方法。第一个是getEvent(),它返回对event对象的引用。考虑到IE中事件对象的位置不同,可以使用这个方法来取得event对象,而不必担心指定事件处理程序的方式。在使用这个方法时,必须假设有一个事件对象传入到事件处理程序中,而且要把该变量传给这个方法:
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
};
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
};
var link = document.getElementById("myLink");
link.onclick = function(event) {
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event) {
alert("Body clicked");
};