事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。
一 传统事件绑定的问题
传统事件绑定中的内联模型不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。传统绑定如:
1 2 3 4 5 6 | window.onload= function (){
var box=document.getElementById( 'box' );
box.onclick = function (){
alert( 'Lee' );
};
};
|
登录后复制
问题一:一个事件处理函数触发两次事件
如果一个页面有两个或者多个js,并且第一个js是第一个程序开发的,第二个js是第二个程序员开发的。第一个window.onload被覆盖了,如
1 2 3 4 5 6 7 | window.onload= function (){
alert( 'Lee' );
};
window.onload= function (){
alert( 'Mr.lee' );
}
|
登录后复制
结果只是打印了 Mr.lee
其实是有办法解决这个问题的,看下面这两种形式。
a:
1 2 3 4 5 6 7 8 9 10 11 | alert(window.onload);
window.onload= function (){
alert( 'Lee' );
};
alert(window.onload);
window.onload= function (){
alert( 'Mr.lee' );
}
|
登录后复制
b:
1 2 3 4 5 6 7 8 9 10 11 | alert(typeof window.onload);
window.onload= function (){
alert( 'Lee' );
};
alert(typeof window.onload);
window.onload= function (){
alert( 'Mr.lee' );
}
|
登录后复制
所以解决办法有了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | window.onload= function (){
alert( 'Lee' );
};
if (typeof window.onload== 'function' ){
var saved=null;
saved=window.onload;
}
window.onload= function (){
if (saved){
saved();
}
alert( 'Mr.lee' );
}
|
登录后复制
问题二:事件切换器
切换一个id为box的div,让里面的背景red与blue直接切换,并且切换之前弹框一次,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | window.onload= function (){
var box=document.getElementById( 'box' );
box.className= "red" ;
box.onclick= function (){
alert( 'Lee' );
blue.call(this);
};
}
function blue(){
this.className= "blue" ;
this.onclick=red;
}
function red(){
this.className= "red" ;
this.onclick=blue;
}
|
登录后复制
上面的代码虽然实现了切换功能,但是弹框只执行了一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | function addEvent(obj,type,fn){
var saved=null;
if (typeof obj[ 'on' +type]== 'function' ){
saved=obj[ 'on' +type];
}
obj[ 'on' +type]= function (){
if (saved){
saved();
}
fn.call(this);
}
}
addEvent(window, 'load' , function (){
var box=document.getElementById( "box" );
addEvent(box, 'click' ,blue);
});
function red(){
this.className= "red" ;
addEvent(box, 'click' ,blue);
}
function blue(){
this.className= "blue" ;
addEvent(box, 'click' ,red);
}
|
登录后复制
按照上面的代码出现了注释中的错误,解决的办法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | function addEvent(obj,type,fn){
var saved=null;
if (typeof obj[ 'on' +type]== 'function' ){
saved=obj[ 'on' +type];
}
obj[ 'on' +type]= function (){
if (saved){
saved();
}
fn.call(this);
}
}
function removeEvent(obj,type){
if (obj[ 'on' +type]){
obj[ 'on' +type]=null;
}
}
addEvent(window, 'load' , function (){
var box=document.getElementById( "box" );
addEvent(box, 'click' ,blue);
});
function red(){
this.className= "red" ;
removeEvent(this, 'click' );
addEvent(box, 'click' ,blue);
}
function blue(){
this.className= "blue" ;
removeEvent(this, 'click' );
addEvent(box, 'click' ,red);
}
|
登录后复制
二 W3C事件处理函数
addEventListener()与removeEventListener()
W3C事件处理函数两个,addEventListener()与removeEventListener()。
//W3C自带的两个添加事件和删除事件
1.覆盖问题,解决
1 2 3 4 5 6 7 8 9 10 11 | window.addEventListener( 'load' , function (){
alert( 'Lee' );
},false);
window.addEventListener( 'load' , function (){
alert( 'Mr.Lee' );
},false);
window.addEventListener( 'load' , function (){
alert( 'Mrs.Lee' );
},false);
|
登录后复制
2.相同函数屏蔽的问题,解决
1 2 3 4 5 6 | window.addEventListener( 'load' ,init,false);
window.addEventListener( 'load' ,init,false);
window.addEventListener( 'load' ,init,false);
function init(){
alert( 'Lee' );
}
|
登录后复制
3.是否可以传递this,解决
例子1:
1 2 3 4 5 6 | window.addEventListener( 'load' , function (){
var box=document.getElementById( 'box' );
box.addEventListener( 'click' , function (){
alert(this);
},false);
},false);
|
登录后复制
例子2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | window.addEventListener( 'load' , function (){
var box=document.getElementById( 'box' );
box.addEventListener( 'click' ,blue,false);
},false);
function red(){
this.className= "red" ;
this.removeEventListener( 'click' ,red,false);
this.addEventListener( 'click' ,blue,false);
}
function blue(){
this.className= "blue" ;
this.removeEventListener( 'click' ,blue,false);
this.addEventListener( 'click' ,red,false);
}
|
登录后复制
4.添加一个额外的方法,会不会被覆盖,或者只能执行一次,解决
1 2 3 4 5 6 7 | window.addEventListener( 'load' , function (){
var box=document.getElementById( 'box' );
box.addEventListener( 'click' , function (){
alert( 'Lee' );
},false);
box.addEventListener( 'click' ,blue,false);
},false);
|
登录后复制
综上所述:W3C是比较完美的解决了这些问题,非常好用,但是IE8和之前的浏览器并不支持,而是采用了自己的事件,当然IE9已经完全支持了W3C的这两个事件处理函数。
W3C可以设置冒泡和捕获方式。
支持W3C标准的浏览器在添加事件时用addEventListener(event,fn,useCapture)方法,基中第3个参数useCapture是一个Boolean值,用来设置事件是在事件捕获时执行,还是事件冒泡时执行。而不兼容W3C的浏览器(IE)用attachEvent()方法,此方法没有相关设置,不过IE的事件模型默认是在事件冒泡时执行的,也就是在useCapture等于false的时候执行,所以把在处理事件时把useCapture设置为false是比较安全,也实现兼容浏览器的效果。
事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。
事件冒泡阶段:事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。
事件的传播是可以阻止的:
在W3c中,使用stopPropagation()方法
在IE下设置cancelBubble = true;
三.IE事件处理函数
attachEvent()和detachEvent()
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE不支持捕获,只支持冒泡;2.IE添加事件不能屏蔽重复的函数;3.IE中的this指向的是window而不是DOM对象。4.在传统事件上,IE是无法接受到event对象的,但使用了attchEvent却可以,但有些区别。
1.覆盖问题,解决了,但有不同,结果是Mrs.Lee,Mr.Lee,最后是Lee
1 2 3 4 5 6 7 8 9 10 | window.attachEvent( 'onload' , function (){
alert( 'Lee' );
});
window.attachEvent( 'onload' , function (){
alert( 'Mr.Lee' );
});
window.attachEvent( 'onload' , function (){
alert( 'Mrs.Lee' );
});
|
登录后复制
2.相同函数屏蔽的问题,未解决。
1 2 3 4 5 6 | window.attachEvent( 'onload' ,init);
window.attachEvent( 'onload' ,init);
function init(){
alert( 'Lee' );
}
|
登录后复制
3.是否可以传递this,不能,this指的是window。需要用call方法。
1 2 3 4 5 6 7 | window.attachEvent( 'onload' , function (){
var box=document.getElementById( 'box' );
box.attachEvent( 'onclick' , function (){
alert(this===window);
});
});
|
登录后复制
下面还有办法就是通过window.event.srcElement。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | window.attachEvent( 'onload' , function (){
var box=document.getElementById( 'box' );
box.attachEvent( 'onclick' ,blue);
});
function red(){
var that=window.event.srcElement;
that.className= "red" ;
that.detachEvent( 'onclick' ,red);
that.attachEvent( 'onclick' ,blue);
}
function blue(){
var that=window.event.srcElement;
that.className= "blue" ;
that.detachEvent( 'onclick' ,blue);
that.attachEvent( 'onclick' ,red);
}
|
登录后复制
4.添加一个额外的方法,会不会被覆盖,或者只能执行一次,解决。
在传统绑定上,IE是无法像W3C那样通过传参接受event对象,但是使用attachEvent()却可以。
1 2 3 4 5 6 7 8 9 10 11 12 | window.attachEvent( 'onload' , function (){
var box=document.getElementById( 'box' );
box.onclick= function (evt){
alert(evt);
}
box.attachEvent( 'onclick' , function (evt){
alert(evt);
alert(evt.type);
alert(evt.srcElement.tagName);
alert(window.event.srcElement.tagName);
});
});
|
登录后复制
跨浏览器的兼容
跨浏览器添加事件
1 2 3 4 5 6 7 | function addEvent(obj,type,fn){
if (obj.addEventListener){
obj.addEventListener(type,fn,false);
} else if (obj.attachEvent){
obj.attachEvent( 'on' +type,fn);
}
}
|
登录后复制
跨浏览器移除事件
1 2 3 4 5 6 7 | function removeEvent(obj,type,fn){
if (obj.removeEventListener){
obj.removeEventListener(type,fn,false);
} else if (obj.detachEvent){
obj.detachEvent( 'on' +type,fn);
}
}
|
登录后复制
跨浏览器获取目标对象
1 2 3 4 5 6 7 | function getTarget(evt){
if (evt.target){
return evt.target;
} else if (window.event.srcElement){
return window.event.srcElement;
}
}
|
登录后复制
调用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | addEvent(window, 'load' , function (){
var box=document.getElementById( 'box' );
addEvent(box, 'click' ,blue);
});
function red(evt){
var that=getTarget(evt);
that.className= "red" ;
removeEvent(that, 'click' ,red);
addEvent(that, 'click' ,blue);
}
function blue(evt){
var that=getTarget(evt);
that.className= "blue" ;
removeEvent(that, 'click' ,blue);
addEvent(that, 'click' ,red);
}
|
登录后复制
四.事件对象的其他补充
relatedTarget事件
w3c中的一个relatedTarget事件。
例如:
1 2 3 4 5 6 7 8 9 10 | addEvent(window, 'load' , function (){
var box=document.getElementById( 'box' );
addEvent(box, 'mouseover' , function (evt){
alert(evt.relatedTarget);
});
addEvent(box, 'mouseout' , function (evt){
alert(evt.relatedTarget);
});
});
|
登录后复制
IE提供了两组分别用于移入移出的属性fromElement和toElement,分别对应mouseover和mouseout。
1 2 3 4 5 6 7 8 9 10 | addEvent(window, 'load' , function (){
var box=document.getElementById( 'box' );
addEvent(box, 'mouseover' , function (){
alert(window.event.fromElement.tagName);
});
addEvent(box, 'mouseout' , function (){
alert(window.event.toElement.tagName);
});
});
|
登录后复制
PS:fromElement和toElement如果分别对应相反的鼠标事件,没有任何意义。
剩下要做的就是跨浏览器兼容操作:
1 2 3 4 5 6 7 8 9 10 11 12 | function getTarget(evt){
var e=evt || window.event;
if (e.srcElment){
if (e.type== 'mouseover' ){
return e.fromElement.tagName;
} else if (e.type= "mouseout" ){
return e.toElement.tagName;
}
} else if (e.relatedTarget){
return e.relatedTarget;
}
}
|
登录后复制
屏蔽跳转操作
取消事件的默认行为有一种不规范的做法,就是返回false。
1 2 3 4 | link.onclick= function (){
alert( 'Lee' );
return false;
}
|
登录后复制
PS:虽然return false;可以实现这个功能,但是有漏洞。
第一:必须写到最后,这样导致中奖的代码执行后,有可能执行不到return false;
第二:return false 写到最前那么之后的自定义操作就失效了。
所以最好的办法应该是在最前面就阻止默认行为,并且后面的代码还可以执行。
1 2 3 4 5 6 7 8 9 | link.onclick= function (evt){
evt.preventDefault;
alert( 'Lee' );
}
link.onclick= function (evt){
window.event.returnValue=false;
alert( 'Lee' );
}
|
登录后复制
那么跨浏览器的兼容:
1 2 3 4 5 6 7 8 | function preDef(evt){
var e=evt || window.event;
if (e.preventDefault){
e.preventDefault();
} else {
e.returnValue=false;
}
}
|
登录后复制
右键菜单contextmenu
兼容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function preDef(evt){
var e=evt || window.event;
if (e.preventDefault){
e.preventDefault();
} else {
e.returnValue=false;
}
}
addEvent(window, "load" , function (){
var body=document.getElementsByTagName( 'body' )[0];
addEvent(body, 'contextmenu' , function (evt){
preDef(evt);
})
});
|
登录后复制
PS:contextmenu事件很常用,这直接导致浏览器兼容性较为稳定。
卸载前事件:beforeunload
这个事件可以帮助在离开本页的时候给出相应的提示,“离开”或者“返回”操作。
1 2 3 | addEvent(window, 'beforeonload' , function (){
preDef(evt);
});
|
登录后复制
鼠标滚轮(mousewheel)和DOMMouseScroll
用于获取鼠标上下滚轮的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | addEvent(document, 'mousewheel' , function (evt){
alert(getWD(evt));
});
addEvent(document, 'DOMMouseScroll' , function (evt){
alert(getWD(evt));
});
function getWD(evt){
var e=evt|| window.event;
if (e.wheelDelta){
return e.wheelDelta;
} else if (e.detail){
return -evt.detail*30;
}
}
|
登录后复制
PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll。
DOMContentLoaded事件和readystatechange事件
DOMContentLoaded事件和readystatechange事件,有关DOM加载方面的事件。