一、传统事件绑定的问题
1、一个事件处理函数被多次赋值会被覆盖
//第一个js文件中的window.onload
window.onload = function(){
alert('lee');
};
//第二个js文件中的window.onload 会覆盖掉第一个
window.onload = function(){
alert('wang');
};
当两组程序或两个JS文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致前面的window.onload完全失效了。
问题解决:
//window.onload 监听window的加载事件,加载完成就调用执行函数。
window.onload = function () {
alert('第一次弹窗');
};
//在后面再给绑定对象的onload属性赋值的时候会覆盖前边的执行函数
//将第一次监听事件的执行函数保存起来
//目的是将所有的执行函数都集中到最后的执行函数当中
if(typeof window.onload == 'function'){
var save = window.onload;
}
//这里给window.onload 赋值 覆盖了前边的window.onload的值。
//这里是最后一次监听
window.onload = function(){
if(save) save();
alert('第二次弹窗');
};
2、事件切换器
window.onload = function(){
var box = document.getElementById('box');
//点击的时候切换class,完成css样式表的切换来改变元素的样式
//只有一个执行函数 只能完成一个事件
box.onclick = function(){
box.className = 'blue';
}
};
//定义方法来完成 连续切换
window.onload = function(){
var box = document.getElementById('box');
box.onclick = toBlue;
};
function toRed (){
//完成class更改
this.className = 'red';
//对onclick重新赋值,下次点击执行toBlue方法
this.onclick = toBlue;
}
function toBlue (){
//完成class更改
this.className = 'blue';
//对onclick重新赋值,下次点击执行toRed方法
this.onclick = toRed;
}
这个切换器在扩展的时候,会出现一些问题:
1.如果增加一个执行函数,那么会被覆盖
window.onload = function(){
var box = document.getElementById('box');
box.onclick = toAlert;//增加的执行函数,会被后边的覆盖掉
box.onclick = toBlue;
};
2.如果解决覆盖问题,就必须同时包含在执行函数,但又出新问题
window.onload = function(){
var box = document.getElementById('box');
box.onclick = function(){
toAlert();//包含进来,可读性降低。而且第一次会被执行,但第二次又会被覆盖,也就是只能执行一次。
//toBlue的函数为:
/* function(){
this.className='blue';
this.onclick = toRed;
}*/
//这里的问题是this的指向问题。this指向的不是box而是window
toBlue();//不能使用这种写法,因为this指向问题。
toBlue.call(this);//这里的this指向的是box;
};
};
当为这个事件切换器扩展时有三个问题:覆盖问题、可读性问题、this传递问题。为解决这三个问题尝试创建一个自定义的方法来进行事件扩展
先来看一下自定义函数的原理
//方法一:
function toBlue (){
this.className = 'blue';
//this.onclick = toRed;
}
//方法二:
function toRed(){
this.className = 'red';
//this.onclick = toBlue;
}
//方法三:
function toAlert(){
alert('lee');
}
//自定义的方法:
//1.传递三个参数:绑定对象、监听事件、扩展方法
//2.为同一个绑定对象的同一个监听事件添加一个或多个都可被执行的扩展方法(比如给window添加了单击事件,当单击发生时这些添加的fn会全部执行而不会发生覆盖)
//obj:绑定对象 比如window
//event:监听事件 比如click
//fn:扩展方法
function addEvent(obj,event,fn){
var save = null;//save的目的是存储上个执行函数(执行函数里包含的是扩展方法)
if(typeof obj['on'+event] == 'function'){//如果上个执行函数存在就将执行函数存储
save = obj['on'+event];//将上个执行函数存储
}
obj['on'+event] = function(){//覆盖掉旧的执行函数,重新为监听事件添加新的执行函数
if(save) save();//如果save不为null,证明存储了上个执行函数,执行上个执行函数。
fn.call(this);//执行扩展方法
}
}
window.onload = function(){
var box = document.getElementById('box');
addEvent(box,'click',toBlue);
//第一次执行:
/* box.onclick = function(){
toBlue.call(this);
}*/
addEvent(box,'click',toRed);
//第二次执行:
/*box.onclick = function(){
(function(){
toBlue.call(this);
})();
toRed.call(this);
}*/
addEvent(box,'click',toAlert());
//第三次执行:
/*box.onclick = function(){
(function(){
(function(){
toBlue.call(
})();
toRed.call(this);
})();
toAlert.call(this);
}*/
};
//从以上执行的流程可以看出每次扩展一个新的fn都会先把上次的执行函数存储起来,在新的执行函数中运行,来达到能执行所有的fn;
二、W3C 事件处理函数
W3C 的现代事件绑定比我们自定义的好处就是 :1.不需要自定义了;2.可以屏蔽相同的函数; 3.可以设置冒泡和捕获。
W3C 的现代事件绑定解决了覆盖问题、屏蔽问题、this传值问题、代码可读性问题。
//方法一:
function toRed(){
this.className = 'red';
}
//方法二:
function toBlue(){
this.className = 'blue';
}
//方法三:
function toAlert(){
alert('lee');
}
window.addEventListener('load',function(){//添加扩展事件
var box = document.getElementById('box');
box.addEventListener('click',toBlue,false);//false表示冒泡 若为true表示捕获
box.addEventListener('click',toAlert,false);
box.addEventListener('click',toAlert,false);//重复事件会被屏蔽掉
box.addEventListener('click',toRed,false);
//PS:以上的所有事件(除了屏蔽掉的)都会别执行。
}
,false);
2、删除扩展事件
//方法一:
function toRed(){
this.className = 'red';
this.removeEventListener('click',toRed);//移除扩展的事件
this.addEventListener('click',toBlue);//添加扩展事件
}
//方法二:
function toBlue(){
this.className = 'blue';
this.removeEventListener('click',toBlue);//移除扩展的事件
this.addEventListener('click',toRed);//添加扩展事件
}
//方法三:
function toAlert(){
alert('lee');
}
window.addEventListener('load',function(){//添加扩展事件
var box = document.getElementById('box');
box.addEventListener('click',toBlue,false);//false表示冒泡 若为true表示捕获
box.addEventListener('click',toAlert,false);
}
,false);
PS:
IE 实现了与 DOM 中类似的两个方法:attachEvent()和 detachEvent()。这两个方法接受相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE 不支持捕获,只支持冒泡;2.IE 添加事件不能屏蔽重复的函数; 3.IE 中的 this 指向的是 window 而不是 DOM 对象。4.在传统事件上,IE 是无法接受到 event 对象的,但使用了 attchEvent()却可以,但有些区别。
IE 中的事件绑定函数 attachEvent()和 detachEvent()可能在实践中不去使用 ,有几个原因:1.IE9 就将全面支持 W3C 中的事件绑定函数;2.IE 的事件绑定函数无法传递 this;3.IE的事件绑定函数不支持捕获 ;4.同一个函数注册绑定后 ,没有屏蔽掉 ;5.有内存泄漏的问题 。
三、事件对象的其他补充
window.addEventListener('load',function(){
var box =document.getElementById('box');
box.onmouseout = function(evt){
alert('来到了'+ evt.relatedTarget );//evt.relatedTarget为从box移出后来到的对象
}
box.onmouseover = function(evt){
alert('来自于' + evt.relatedTarget);//evt.relatedTarget为到box之前的对象
};
},false);
IE有自己单独的移入移出事件对象属性,下面给出兼容代码
function getTarget(evt) {
var e = evt || window.event; //得到事件对象
if (e.srcElement) {//如果支持 srcElement,表示 IE
if (e.type == 'mouseover') { //如果是 over
return e.fromElement;//就使用 from
}
else if (e.type == 'mouseout') {//如果是 out
return e.toElement;//就使用 to
}
}
else if (e.relatedTarget) { //如果支持 relatedTarget,表示 W3C
return e.relatedTarget;
}
}
2、取消事件默认行为
有时我们需要阻止事件的默认行为 ,比如:一个超链接的默认行为就点击然后跳转到指定的页面。那么阻止默认行为就可以屏蔽跳转的这种操作,而实现自定义操作。
取消事件默认行为还有一种不规范的做法,就是返回 false。
link.onclick = function () {
alert('Lee');
return false; //直接给个假,就不会跳转了。
//PS:虽然 return false;可以实现这个功能,但有漏洞;
//第一:必须写到最后,这样导致中间的代码执行后,有可能执行不到 return false;
//第二:return false 写到最前那么之后的自定义操作就失效了。所以,最好的方法应该是在最前面就阻止默认行为 ,并且后面还能执行代码。
};
//W3C,阻止默认行为,放哪里都可以
link.onclick = function (evt) {
evt.preventDefault();
alert('Lee');
};
//IE,阻止默认行为
link.onclick = function (evt) {
window.event.returnValue= false;
alert('Lee');
};
跨浏览器兼容
function preDef(evt) {
var e = evt || window.event;
if (e.preventDefault) {//W3C,阻止默认行为,放哪里都可以
e.preventDefault();
}
else {//IE,阻止默认行为
e.returnValue= false;
}
}
3、 上下文菜单事件: contextmenu
window.addEventListener('load', function () {
var text = document.getElementById('text');
text.addEventListener('contextmenu', function (evt) {
var e = evt || window.event;
preDef(e);
var menu = document.getElementById('menu');
menu.style.left = e.clientX + 'px';
menu.style.top = e.clientY + 'px';
menu.style.display = 'block';
});
document.addEventListener( 'dblclick', function () {
document.getElementById('menu').style.display = 'none';
});
});
4、鼠标滚轴事件
document.addEventListener( 'mousewheel', function (evt) {
//非火狐
alert(getWD(evt));
});
document.addEventListener('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;//计算保持统一
}
}