这周由我给大家分享javascript 事件监听。为什么要采用事件监听而不是直接对元素的事件属性(如:onclick、onmouseover)赋值?这两种方法处理事件还是有很大区别的!事件属性只能赋值一种方法,假如我们要给页面onload事件绑定一个方法A,那么我们会写window.onload=A,然后我们再想给onload事件绑定一个方法B,如果我们写成window.onload=B,那么之前绑定的方法A就会被覆盖掉:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript">
function A(){
console.log("the method A");
}
function B(){
console.log("the method B");
}
window.onload=A;
window.onload=B;
</script>
</head>
</html>
怎么给onolad事件绑定多个方法呢?我们可以用一个匿名函数来将所要执行的函数依次放入这个匿名函数里面,例如:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript">
function A(){
console.log("the method A");
}
function B(){
console.log("the method B");
}
function AddEvent(method){
var oldLoad=window.onload;
if((typeof window.onload) == "function"){
window.onload=function(){
oldLoad();
method();
}
}else{
window.onload = method;
}
}
AddEvent(A);
AddEvent(B);
</script>
</head>
</html>
除了这种方法,JS已经为我们准备了像attachEvnet、addEventListener这样的方法来满足我们的需求,虽然在不同级别的浏览器中方法不尽相同,利用对browser的能力检测还是可以轻松的实现一个兼容的方法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript">
function BindEvent(element,type,fn){
if (element.addEventListener){
element.addEventListener(type, fn, false);
} else if (element.attachEvent){
element.attachEvent('on'+type, fn);
}
else{
element["on"+type]=fn;
}
}
function A(){
alert("the method A");
}
function B(){
alert("the method B");
}
window.onload=function(){
var obj = document.getElementById("demo");
BindEvent(obj,"click",A);
BindEvent(obj,"click",B);
}
</script>
<style type="text/css">
.demo{
width:100px;
height:100px;
background-color:red;
}
</style>
</head>
<body>
<div class="demo" id="demo">
</div>
</body>
</html>
在attachEvent监听事件函数中,this指向的是window,请看如下代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script type="text/javascript">
function A(){
alert(this +" "+ this.tagName);
}
window.onload=function(){
var obj=document.getElementById("demo");
if(obj.addEventListener){
obj.addEventListener("click",A,false);
}else if(obj.attachEvent){
obj.attachEvent("onclick",A);
}
}
</script>
</head>
<body>
<div style="width:100px;height:100px;background-color:red;" id="demo">
</div>
</body>
</html>
如何来修正这个this呢?我们可以这样干:
<script type="text/javascript">
function A(){
alert(this +" "+ this.tagName);
}
window.onload=function(){
var obj=document.getElementById("demo");
if(obj.addEventListener){
obj.addEventListener("click",A,false);
}else if(obj.attachEvent){
obj.attachEvent("onclick",function(){
A.apply(obj,arguments);
});
}
}
</script>
通过apply来纠正this是比较靠谱的,这样做可以很好的解决这个this指向的问题,但是问题又来了,我们移除事件是通过detachEvent来移除,这个方法必须传入要移除的方法名,而我们这样纠正this是通过匿名函数来做到的,匿名函数就不好被移除了。。。。这怎么办呢?查了各方资料,还真没办法移除这个匿名函数。。。有个解决办法就是将
function(){
A.apply(obj,arguments);
}
赋值给一个变量入var b=function(){A.apply(obj,arguments);}然后再对这个b做绑定。
这里给出一个比较好的兼容写法:
function addEvent(obj, evType, fn, useCapture)
{
//-- Default to event bubbling
if (!useCapture) useCapture = false;
//-- DOM level 2 method
if (obj.addEventListener)
{
obj.addEventListener(evType, fn, useCapture);
}
else
{
//-- event capturing not supported
if (useCapture)
{
alert('This browser does not support event capturing!');
}
else
{
var evTypeRef = '__' + evType;
//-- create function stack in the DOM space of the element; seperate stacks for each
event type
if (!obj[evTypeRef])
{
//-- create the stack if it doesn't exist yet
obj[evTypeRef] = [];
//-- if there is an inline event defined store it in the stack
var orgEvent = obj['on'+evType];
if (orgEvent) obj[evTypeRef][0] = orgEvent;
//-- attach helper function using the DOM level 0 method
obj['on'+evType] = IEEventHandler;
}
else
{
//-- check if handler is not already attached, don't attach the same function twice to match
behavior of addEventListener
for (var ref in obj[evTypeRef])
{
if (obj[evTypeRef][ref] === fn) return;
}
}
//-- add reference to the function to the stack
obj[evTypeRef][obj[evTypeRef].length] = fn;
}
}
}
/**
* removeEvent
*
* Generic function to remove previously attached event listeners.
*
* @param obj The object to which the event listener was attached.
* @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
* @param fn The listener function.
* @param useCapture (optional) Whether event capturing, or event bubbling
* (default) was used.
*/
function removeEvent(obj, evType, fn, useCapture)
{
//-- Default to event bubbling
if (!useCapture) useCapture = false;
//-- DOM level 2 method
if (obj.removeEventListener)
{
obj.removeEventListener(evType, fn, useCapture);
}
else
{
var evTypeRef = '__' + evType;
//-- Check if there is a stack of function references for this event type on the object
if (obj[evTypeRef])
{
//-- iterate through the stack
for (var ref in obj[evTypeRef])
{
//-- if function reference is found, remove it
if (obj[evTypeRef][ref] === fn)
{
try
{
delete obj[evTypeRef][ref];
}
catch(e)
{
obj[evTypeRef][ref] = null;
}
return;
}
}
}
}
}
/**
* IEEventHandler
*
* IE helper function to execute the attached handlers for events.
* Because of the way this helperfunction is attached to the object (using the DOM
* level 0 method)
* the 'this' keyword will correctely point to the element that the handler was
* defined on.
*
* @param e (optional) Event object, defaults to window.event object when not passed
* as argument (IE).
*/
function IEEventHandler(e)
{
e = e || window.event;
var evTypeRef = '__' + e.type;
//-- check if there is a custom function stack defined for this event type on the object
if (this[evTypeRef])
{
//-- iterate through the stack and execute each function in the scope of the object by using
function.call
for (var ref in this[evTypeRef])
{
if (Function.call)
{
this[evTypeRef][ref].call(this, e);
}
else
{
//-- IE 5.0 doesn't support call or apply, so use this
this.__fn = this[evTypeRef][ref];
this.__fn(e);
this.__fn = null;
}
}
}
}
这里再推荐一种用到attachEvent和detachEvent的比较好的兼容方法:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj["e"+type+fn] = fn;
obj[type+fn] = function(){obj["e"+type+fn]( window.event );}
obj.attachEvent("on"+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent("on"+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
PS:用attachEvent绑定的事件,执行起来是无序的(网上人这么说,我的亲身实践不是无序的,是倒叙的。。。。。),所以推荐大家绑定一个事件函数,再执行一个有序队列比较好。。。
最后说一下addEventListener这个方法的第三个参数,它是一个布尔值,标志着监听是捕获阶段还是冒泡阶段。默认是false,为冒泡阶段,这样可以最大程度的兼容浏览器,因为IE浏览器只有冒泡阶段,无捕获阶段。