javascript事件绑定学习要点


Posted in Javascript onMarch 09, 2016

事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。

一 传统事件绑定的问题

传统事件绑定中的内联模型不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。传统绑定如:

window.onload=function(){
 var box=document.getElementById('box');
 box.onclick = function(){
  alert('Lee');
 };
};

问题一:一个事件处理函数触发两次事件

如果一个页面有两个或者多个js,并且第一个js是第一个程序开发的,第二个js是第二个程序员开发的。第一个window.onload被覆盖了,如

window.onload=function(){
 alert('Lee');
};

window.onload=function(){
 alert('Mr.lee');
}

结果只是打印了 Mr.lee

其实是有办法解决这个问题的,看下面这两种形式。
a:

alert(window.onload);//一开始没有注册window.onload,那么就是null

window.onload=function(){
 alert('Lee');
};

alert(window.onload);//如果已经有window.onload,打印的是函数function

window.onload=function(){
 alert('Mr.lee');
}

b:

alert(typeof window.onload);//一开始没有window.onolad,旧版火狐显示undefined,新版显示object,

window.onload=function(){
 alert('Lee');
};

alert(typeof window.onload);//如果已经有window.onload,所有浏览器都会显示function

window.onload=function(){
 alert('Mr.lee');
}

所以解决办法有了。

window.onload=function(){
 alert('Lee');
};

if(typeof window.onload=='function'){
 var saved=null;//保存上一个事件对象
 saved=window.onload;
}

//saved 就是window.onload,saved()相当于window.onload(),但是window.onload()不能执行的
//所以saved()相当于window.onload=function(){}

window.onload=function(){
 if(saved){
  saved();//执行上一个事件 window.onload=function(){}
 }
 alert('Mr.lee'); //执行本事件
}

问题二:事件切换器
切换一个id为box的div,让里面的背景red与blue直接切换,并且切换之前弹框一次,如:

window.onload=function(){
 var box=document.getElementById('box');
 box.className="red";
 box.onclick=function(){
  alert('Lee'); //只执行了一次
  blue.call(this);//通过匿名函数执行某一函数,那么里面的this就是代表的window,所以可以通过call传递
 };
}

function blue(){
 this.className="blue";
 this.onclick=red;
 
}

function red(){
 this.className="red";
 this.onclick=blue;
}

上面的代码虽然实现了切换功能,但是弹框只执行了一次。

//添加事件函数
//obj相当于window
//type相当于onload
//fn相当于function(){}
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',function(){ //目的达到,每次都执行了,没有被覆盖
 // alert('ss');
 //});
 addEvent(box,'click',blue);
});

function red(){
 this.className="red";
 addEvent(box,'click',blue);
}

function blue(){
 this.className="blue";
 addEvent(box,'click',red);
}

//当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
//因为积累了太多的保存的事件
//解决方案,就是用完的事件,就立刻移除掉

按照上面的代码出现了注释中的错误,解决的办法如下:

//添加事件函数
//obj相当于window
//type相当于onload
//fn相当于function(){}
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);
 }
 
}


//当不停的切换的时候,浏览器突然卡死,并且报错:too much recursion,太多的递归
//因为积累了太多的保存的事件
//解决方案,就是用完的事件,就立刻移除掉


//移除事件函数
function removeEvent(obj,type){
 if(obj['on'+type]){
  obj['on'+type]=null;
 }
}


addEvent(window,'load',function(){
 var box=document.getElementById("box");
 //addEvent(box,'click',function(){ //目的达到,每次都执行了,没有被覆盖
 // alert('ss');
 //});
 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.覆盖问题,解决

window.addEventListener('load',function(){
 alert('Lee');
},false);

window.addEventListener('load',function(){
 alert('Mr.Lee');
},false);

window.addEventListener('load',function(){
 alert('Mrs.Lee');
},false);

2.相同函数屏蔽的问题,解决

window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
function init(){
 alert('Lee');
}

3.是否可以传递this,解决
例子1:

window.addEventListener('load',function(){
 var box=document.getElementById('box');
 box.addEventListener('click',function(){
  alert(this);
 },false);
},false);

例子2:

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.添加一个额外的方法,会不会被覆盖,或者只能执行一次,解决

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

window.attachEvent('onload',function(){
 alert('Lee');
});

window.attachEvent('onload',function(){
 alert('Mr.Lee');
});
window.attachEvent('onload',function(){
 alert('Mrs.Lee');
});

2.相同函数屏蔽的问题,未解决。

window.attachEvent('onload',init);
window.attachEvent('onload',init);

function init(){
 alert('Lee');
}

3.是否可以传递this,不能,this指的是window。需要用call方法。

window.attachEvent('onload',function(){
 var box=document.getElementById('box');
 box.attachEvent('onclick',function(){
  //alert(this===box);
  alert(this===window); //true
 });
});

下面还有办法就是通过window.event.srcElement。代码如下:

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()却可以。

window.attachEvent('onload',function(){
 var box=document.getElementById('box');
 box.onclick=function(evt){ //传统方法IE无法通过参数获取evt
  alert(evt);//undefined
 }
 box.attachEvent('onclick',function(evt){
  alert(evt);//object
  alert(evt.type);//click
  alert(evt.srcElement.tagName);//DIV
  alert(window.event.srcElement.tagName);//DIV
 });
});

跨浏览器的兼容

跨浏览器添加事件

function addEvent(obj,type,fn){
 if(obj.addEventListener){
  obj.addEventListener(type,fn,false);
 }else if(obj.attachEvent){
  obj.attachEvent('on'+type,fn);
 }
}

跨浏览器移除事件

function removeEvent(obj,type,fn){
 if(obj.removeEventListener){
  obj.removeEventListener(type,fn,false);
 }else if(obj.detachEvent){
  obj.detachEvent('on'+type,fn);
 }
}

跨浏览器获取目标对象

function getTarget(evt){
 if(evt.target){
  return evt.target;
 }else if(window.event.srcElement){
  return window.event.srcElement;
 }
}

调用方式:

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事件。
例如:

addEvent(window,'load',function(){
 var box=document.getElementById('box');
 addEvent(box,'mouseover',function(evt){
  alert(evt.relatedTarget); //得到移入box最近的那个DOM对象
 });
 
 addEvent(box,'mouseout',function(evt){
  alert(evt.relatedTarget); //从box移出最近的那个DOM对象
 });
});

IE提供了两组分别用于移入移出的属性fromElement和toElement,分别对应mouseover和mouseout。

addEvent(window,'load',function(){
 var box=document.getElementById('box');
 addEvent(box,'mouseover',function(){
  alert(window.event.fromElement.tagName); //得到移入box最近的那个DOM对象
 });
 
 addEvent(box,'mouseout',function(){
  alert(window.event.toElement.tagName); //从box移出最近的那个DOM对象
 });
});

PS:fromElement和toElement如果分别对应相反的鼠标事件,没有任何意义。

剩下要做的就是跨浏览器兼容操作:

function getTarget(evt){
 var e=evt || window.event;
 if(e.srcElment){ //IE
  if(e.type=='mouseover'){
   return e.fromElement.tagName;
  }else if(e.type="mouseout"){
   return e.toElement.tagName;
  }
 }else if(e.relatedTarget){ //w3c
  return e.relatedTarget;
 }
}

屏蔽跳转操作

取消事件的默认行为有一种不规范的做法,就是返回false。

link.onclick=function(){
 alert('Lee');
 return false;
}

PS:虽然return false;可以实现这个功能,但是有漏洞。
第一:必须写到最后,这样导致中奖的代码执行后,有可能执行不到return false;
第二:return false 写到最前那么之后的自定义操作就失效了。
所以最好的办法应该是在最前面就阻止默认行为,并且后面的代码还可以执行。

link.onclick=function(evt){
 evt.preventDefault;//w3c,阻止默认行为
 alert('Lee');
}

link.onclick=function(evt){
 window.event.returnValue=false;//IE,阻止默认行为
 alert('Lee');
}

那么跨浏览器的兼容:

function preDef(evt){
 var e=evt || window.event;
 if(e.preventDefault){
  e.preventDefault();
 }else{
  e.returnValue=false;
 }
}

右键菜单contextmenu
兼容:

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
这个事件可以帮助在离开本页的时候给出相应的提示,“离开”或者“返回”操作。

addEvent(window,'beforeonload',function(){
 preDef(evt);
});

鼠标滚轮(mousewheel)和DOMMouseScroll
用于获取鼠标上下滚轮的距离

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加载方面的事件。

Javascript 相关文章推荐
js 解决“options为空或不是对象”
Dec 22 Javascript
jquery1.4.2 for Visual studio 2010 模板文件
Jul 14 Javascript
jQuery LigerUI 使用教程入门篇
Jan 18 Javascript
JS中for循序中延迟加载动态效果的具体实现
Aug 18 Javascript
jquery组件使用中遇到的问题整理及解决
Feb 21 Javascript
JavaScript表单验证的两种实现方法
Feb 11 Javascript
Angular异步变同步处理方法
Aug 13 Javascript
移动端滑动切换组件封装 vue-swiper-router实例详解
Nov 25 Javascript
JavaScript实现的滚动公告特效【基于jQuery】
Jul 10 jQuery
vue 实现路由跳转时更改页面title
Nov 05 Javascript
javascript 原型与原型链的理解及实例分析
Nov 23 Javascript
vue实现登录、注册、退出、跳转等功能
Dec 23 Vue.js
js实现分割上传大文件
Mar 09 #Javascript
js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)
Mar 09 #Javascript
基于jQuery的网页影音播放器jPlayer的基本使用教程
Mar 08 #Javascript
利用jQuery设计一个简单的web音乐播放器的实例分享
Mar 08 #Javascript
Bootstrap~多级导航(级联导航)的实现效果【附代码】
Mar 08 #Javascript
js实现数组冒泡排序、快速排序原理
Mar 08 #Javascript
Bootstrap多级导航栏(级联导航)的实现代码
Mar 08 #Javascript
You might like
处理php自动反斜杠的函数代码
2010/01/05 PHP
php中使用base HTTP验证的方法
2015/04/20 PHP
为你总结一些php信息函数
2015/10/21 PHP
合格的PHP程序员必备技能
2015/11/13 PHP
php禁用函数设置及查看方法详解
2016/07/25 PHP
jQuery 学习第七课 扩展jQuery的功能 插件开发
2010/05/17 Javascript
javascript中tostring()和valueof()的用法及两者的区别
2015/11/16 Javascript
Bootstrap入门书籍之(零)Bootstrap简介
2016/02/17 Javascript
Jquery获取第一个子元素简单实例
2016/06/02 Javascript
JavaScript的this关键字的理解
2016/06/18 Javascript
Node.js读取文件内容示例
2017/03/07 Javascript
Bootstrap学习笔记之进度条、媒体对象实例详解
2017/03/09 Javascript
原生JS上传大文件显示进度条 php上传文件代码
2020/03/27 Javascript
Vue路由钩子之afterEach beforeEach的区别详解
2018/07/15 Javascript
JavaScript实现构造json数组的方法分析
2018/08/17 Javascript
node.js微信小程序配置消息推送的实现
2019/02/13 Javascript
微信小程序收货地址API兼容低版本解决方法
2019/05/18 Javascript
梳理一下vue中的生命周期
2020/12/30 Vue.js
Python标准库之多进程(multiprocessing包)介绍
2014/11/25 Python
Python中每次处理一个字符的5种方法
2015/05/21 Python
python使用xslt提取网页数据的方法
2018/02/23 Python
python实现将一个数组逆序输出的方法
2018/06/25 Python
Python实现的对一个数进行因式分解操作示例
2019/06/27 Python
使用Rasterio读取栅格数据的实例讲解
2019/11/26 Python
Python利用 utf-8-sig 编码格式解决写入 csv 文件乱码问题
2020/02/21 Python
解决python运行启动报错问题
2020/06/01 Python
flask开启多线程的具体方法
2020/08/02 Python
python opencv图像处理(素描、怀旧、光照、流年、滤镜 原理及实现)
2020/12/10 Python
加大码胸罩、内裤和服装:Just My Size
2019/03/21 全球购物
C语言50道问题
2014/10/23 面试题
大学生村官心得体会范文
2014/01/04 职场文书
明星员工获奖感言
2014/08/14 职场文书
优秀教师先进事迹材料
2014/12/15 职场文书
2015年采购员工作总结
2015/04/27 职场文书
2015年大学班主任工作总结
2015/04/30 职场文书
mysql中整数数据类型tinyint详解
2021/12/06 MySQL