什么是事件:
我们可以简单的把事件理解为浏览器的感知系统。比如说:他可以感觉到用户是否点击(click)了页面、鼠标是否进入了页面的某个元素上面(mouseover或mouseenter)、鼠标是否离开了网页(mouseout或mouseleave)、浏览器是都加载完了页面上的资源(window.onload)、文档树是否生成(DOMContentLoaded)、键盘上的某个键是否按下(keydown)、鼠标的滚轮是否滚动了等等。
其实事件的原理并非是浏览器的感觉系统,它的本质是一个行为发生时,对另一个行为的回调。
事件的实现(事件绑定):
事件的绑定就是:当这个事件发生的时候,运行一个或者多个方法(function),比如说当鼠标点击页面的时候,就弹出一个“事件”,则写成:
document.onclick = function(){alert("事件“);}
事件的绑定相当于做计划,绑定在事件上的方法执行了就相当于计划的事发生了,所以一般情况下,事件属性的前面都有”on“,如:ele.onclick, ele.onmousedown, ele.onmouseup, 这里的on,其实就相当于:当什么时候,做计划要早于计划的事件发生。
当然我们也可以不给事件绑定处理方法,也就是说当此事件发生的时候,什么也不需要做,事件常有,而事件上绑定的方法不一定有,
我们给页面中的元素的某个事件绑定处理方法的时候。经常还会有一个形式参数e,但是运行的事件,却没有办法传递实参给这个形参e,比如:
function fn (e){
//标准浏览器中:定义一个形参e,但当事件触发的时候,并没有给e赋实际的值,则浏览器会把”事件“的对象赋给这个形参e,这时这个e是个系统级的对象:事件;
IE中的事件对象是个全局的属性window.event,而标准浏览器的事件对象就是形参e;
所以事件对象的兼容性写法为:e = e||window.event;
以下是常用的事件对象的属性:
var x =e.clientX,y=e.clientY;所有浏览器都支持,相当于浏览器中鼠标的坐标;
var x=e.pageX,y = e.pageY;ie8或以下不支持,相当于文档的中鼠标的坐标;
target事件源;事件源的概念:事件最终发生在页面的那个元素上;
事件源和事件的传播是息息相关的
事件的传播包括:冒泡和捕获;事件传播是浏览器在处理事件行为的机制,冒泡阶段或者捕获阶段。 注意:ie 678里,事件传播只有冒泡阶段;
var target = e.target||e.srcElement 后者是处理ie兼容‘;
div1.innerHTML="当前的事件类型:”+e.type+“鼠标的X坐标:“+x+”鼠标的Y坐标:“+Y+”事件发生在哪儿:“+e.target;
}
document.onmousemove=fn;把烦恼直接赋值给document的onmousemove这个属性
示例2:
var ele = document.getElememtById('div1');
document.onkeydown = function(e){
e=e||wondow.event;//处理事件对象兼容性
//console。log(e.keyCode):keyCode是是当前的这个键值对应的ASCII码
switch(e.keyCode){
case 37://键盘上的左键
ele.style.left = ele.offserLeft-5+"px";
break;
case 38://上
ele.style.top = ele.offfsetTop-5+'px';
break;
}
}
以上这样的事件绑定方法(就是直接把fn赋给document.onmousemove的方式)叫DOM0级事件绑定,它是相当于DOM2级事件绑定来说的
DOM元素的默认行为:
很多的网页元素都会有默认的行为,比如说当你点击一个超链接a0标签的时候,他就会有一个跳转行为;当你在网页上点击鼠标右键的时候会出现一个右键菜单;当你在一个form表单里点击提交按钮时网页会产生一个行为病刷新网页,当你网页上滚动鼠标滚轮的时候,页面的滚动条会滚动等等;这些都叫事件的默认行为,如果想把这些默认行为取消了,相应的js代码如下:
a.onclick = function(){return false}//方法里加个 return false,就是组织超链接点击时的跳转行为了;
document.oncontextmenu = function(){
//在这里可以加一些代码,实现自定义的右键菜单;
return false //系统自带的右键菜单就失效了
}
Form.onsubmit = function(){return false;}//这样表单就不会产生提交行为了;
document.onmousewheel = function(){return false;}//IE和chrome的方式,取消鼠标的滚轮的默认行为,网页的滚动条就不会动了;
document.addEventListener('DOMMouseScoll',function(e){e.preventDefault = true;});火狐的取消滚轮的默认行为;火狐只能用Dom的二级事件绑定方式,并且用e.preventDefault = true;来取消浏览器滚轮的默认行为;
我们要知道常见的事件默认行为有哪些,并且要知道阻止默认行为,只要绑定到这个行为事件的方法最后加一句:return false;就可以了;
但是要强调的是:如果你的事件绑定是用addEventListener来实现的,那阻止默认行为必须用e.preventDefault = true;
事件传播和阻止事件传播:
当事件发生在子元素中的时候,往往会引起连锁反应,就是在它的祖先元素上也会发生这个事件,比如说你点击了一个div,也相当于点击了一个body,同样相当于点击了HTML,同样相当于点击了document,
在理解事件传播的时候要注意两点:
一、是事件本身在传播,而不是绑定在事件上的方法在传播;
二、是并非所有的事件都会传播,像onfocus,onblur等事件就不会传播,onmouseenter和onmouseleave事件也不会传播。
事件委托:
事件委托是利用事件的传播机制,通过判断事件源来实现的,是一种高性能的事件处理方式。对事件委托的好处和概念详见《高程3》的第402页;
我们通过一个简单的示例来看看事件的好处。
需求:在如下的HTML代码中,当你点击这个页面中的一个元素时,弹出这个元素对应的标签名;
<body>
<div id='outer'>
outer
<div id = 'inner'>
inner
<p>ppppp
<span>span
<a href='###'>张松</a>
</span>
</p>
</div>
</div>
</body>
一般的思路是把所有的元素都获取到,然后循环绑定,这样做的缺点就是不仅性能不好,并且还要处理事件传播的问题,不优化的代码如下;
var eles = document.getElementsByTagNmae('*');
for(var i=0;i<eles.length;i++){
eles,item(i).onclick = function(e){
alert(this.tagName);
e.stopPropagation();//加上阻止事件传播是可以的,但是性能不是最优的;
return false;//阻止超链接的默认行为;
}
}
以下用事件委托实现
事件委托:事件委托就是利用事件传播的机制,无论哪一个页面元素,他的click事件都会最终传播到document上;这样,只需要在document上处理click事件即可;
document.onclick = function(e){
e = e||window.event;
var target = e.target||e.srcElement;//获得事件源是关键;
alert(target.nodeName);
return false;
};
事件委托的关键是理解号事件的事件源的概念;
DOM二级事件
DOM 是解决文档里元素关系的一套模式,其实那只是dom的第一个版本解决的问题。在dom的第二个版本里,解决的问题就不仅仅是文档里元素之间的关系了,还把dom元素的事件问题也重新给了一套方案,这套方案就叫做’dom二级事件‘;
DOM二级事件解决了原来的同意事件绑定多个处理方法时,后面绑定的会覆盖前面绑定的问题,如:
ele.onclick = fn1;
ele.onlcik=fn2;
这样的处理的结果就是,ele的onclick事件上,fn2方法把fn1方法给覆盖了,这样就不容易实现同一个事件上绑定多个方法。
W3C给出的方法是:ele.addEventListener('click',fn,false);
ie6/7/8给出的方法是:ele.attachEvent('onclick',fn);
解决标准浏览器和低版本的IE方案如下:
事件绑定:
function bind(ele,type,handler){//标准浏览器
if(ele.addEventListener){
ele.addEventListener('type',handler,false);
}else if(ele.attachEveent){//IE专用
ele.attachEvent('on'+type,handler)
}
}
移除事件绑定:
function unbind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener('type',handler,false);
}else if(ele.detachEvent){
ele.detachEvent('on'+type,handler);
}
}
注意:虽然IE、谷歌、火狐等浏览器都给出了DOM2级事件的处理方法,但是IE的方法却存在着很多问题,并且非常严重。一、被绑定的方法在事件触发执行时,this关键字竟让是window,二、IE中被绑定到事件上的方法的执行顺序是混乱的。
在W3C的标准是在同一事件上,先绑定的方法先执行,并且不能重复绑定同一个方法在同一个事件上,但是IE6、7、8中,如果绑定的方法少于9个,执行的顺序是相反的,超过9个,执行顺序就是混乱的,这些IE中的问题都是比较严重的,我们必须解决好。
事件的兼容性问题总结:(常见的)
* 事件对象本身:标准浏览器是事件发生时自动给方法传递一个实参,这个实参就是事件对象,IE是全局的window.event;
* 阻止事件传播:标准e.stopPropagation这个方法;IE是e.cancelBubble = true这个属性;
* 阻止默认行为:e.preventDefault()方法,IE是e.returnValue = false;
* 事件源:e.target IE是e.srcElement;
* e.pageX ,e.pageY IE不支持这两个属性
*DOM二级的事件绑定e.addEventListener ,IE ele.attachEvent;
*IE 的attachEvent 绑定的方法上:1、this不是当前的元素 2、执行顺序是混乱的
DOM二级事件兼容性问题解决之一:解决this关键字
注意:以下代码中的handler是个形参,他可以表示不同的方法,所以不要把handler认为是某一个具体的方法。如果你bind(ele,'click',fn1),则fn1就是handler,如果bind(ele,'click',fn2),那么fn2就是handler。新手一定要理解好把握好;
关键思路:关键是两个问题,一、理解好call的用途和作用,二、理解好在一个程序里写的代码,是为了解决另一个程序中的问题的这种思路。
我们修改一下上面写过的bind方法;
如果只是解决被绑定的方法的this指向,倒是好办,只需要在IE专用的代码里做如下修改就可以,
else if (ele.attachEvent){
function fnTemp = function(){handler.call(ele)};//使用call方法,强制使handler方法在运行时this指向被绑定的ele这个DOM元素
ele.detachEvent('on'+type,fnTemp);//再绑定时,就不是直接绑定handler这个方法了,而是绑定经过’化装‘的fnTemp这个方法
}
或者直接写:
else if(ele.detachEvent){
ele.detachEvent('on'+type,function(){handler.call(ele)};
}
这样确实在事件触发时,handler运行了,并且让handler的this指向了被绑定的元素ele,但是由于我已经不是直接绑定的handler方法,而是经过call变形后的fnTemp方法,那在移除绑定的时候,我们就没有办法移除handler方法了。
那我们怎么能找到这个化装之后的fntemp,并将其移除呢?这件事必须要在绑定事件的时候就要考虑好,在bind方法里,把fnTemp方法保存下来,并且还要用某种方式识别出来这个fnTemp方法是由那个handler’变形。而来的。
这是个不好解释的难点,做这件事,我们需要两步;
一、把fnTemp保存下来,不能使用全局变量保存,因为容易被污染,保存在bind的某个变量里,局部变量在unbind这个作用域内也访问不到,在不同的作用域里,还能访问到一个不是全局变量的值,那用什么呢?最好的方式就是把fnTemp保存在ele这个DOM元素的属性上,因为这个ele是两个函数都要操作的引用类型的变量,那么我们在ele上定义一个自定义属性,那这个属性在bind和unbind两个作用域里面都能访问到,具体实现方式如下:
else if(ele.attachEvent){
if(!ele['aBind'+type){//如果不存在这个属性则创建一个
ele['aBind'+type] = [];//使用数组来保存被绑定到不同事件上的那些方法(相当的事件上,可能会被绑定很多个handler)
//这个属性是以‘aBind"为前缀,以type为区分符的,Type是事件的类型,这是一个非常重要的技巧
}
var tempFn =function(){handler.call(ele)};让这个方法运行是this关键字指向被绑定的元素;
ele['aBind'+type].push(tempFn);把变形后的tempFn保存在数组里
ele.attachEvent('on'+type,tempFn);
}
补充:理解好ele['aBind'+type]=[]这个属性定义时,’aBind‘这个字符串是区别符的意思。
先看如果没有这个区别符会怎么样?那就定义了ele[type]=[];如果type是’click‘,则会出现ele.click=[];而ele本身就有click这个方法属性,我们就没有办法修改这个原生的属性,这样定义就失效了。所以才给type前面加上'aBind’区别符作为前缀,以避免或减少和原生的属性的冲突,这种加前缀的技巧还是很常见。
二、是给tempFn再添加一个自定义属性,用来标示当前这个tempFn是由handler变形而来的
var tempFn= function(){handler.call(ele);}
tempFn.photo = handler ;//我们通过tempFn的photo这个属性,就可以分辨出tempFn这个方法是由那个handler变形而来的,photo这个属性只是在这定义,而使用它是在unbind函数里。
三、还要强调一个DOM2级事件绑定变成的原则,即:一个函数不能重复绑定在同一个事件上。比如:不能把fn1这个函数重复的绑定给ele的click事件;
ele.addEventListener('click',fn1,false);
ele.addEventListener('click',fn1,false);//绑定两次或者多次,在事件触发时,后面的绑定是无效,
当然,低版本的IE浏览器是没有遵循这个原则的,所以这儿还要解决以下这个问题,加一个判断即可
for(var i=0;i<ele['aBind'+type].length;i++){
if(ele['aBind'+type][i].photo==handler){//如果数组中已经存在了经过化妆的handler方法,则退出循环;
return;//保证一个方法只能被绑定到某个事件上一次;
}
}
Bind方法的完整代买如下:
function bind(ele,type,handler){
if(ele.addEventListener){
ele.addEventListener(type,handler,false);
}else if(ele.attachEvent){
if(!ele['aBind'+type]){ele['aBind'+type]=[];}
var tempFn = function(){handler.call(ele)};
tempFn.photo=handler;
for(var i=0;i<ele['aBind'+type].length;i++){
if(ele['aBind'+type][i].photo ==handler){return;}
}
ele['aBind'+type].push(tempFn);
ele.attachEvent('on'+type,tempFn);
}
}
我们再来看unbind,该做的准备工作,都已经在bind里完成了。unbind负责把绑定的在事件上的方法移除,但现在已经不是移除handler这个方法了,而是移除经过化装后的这个方法。这个方法保存在ele的['aBind'+type这个属性上。
我们知道ele['aBind'+type]这是个数组,先把它取到。赋值给一个短变量a:var a=ele['aBind'+type]。操作a这个短变量比操作ele['aBind'+type]这个属性更方便。
然后遍历这个数组,逐个比较那个是经过化装的handler方法,当然比较的一句是photo这个属性所以是:
for (var i=0;i<a.length;i++){
if(a[i].photo==handler){
ele.detachEvent('on'+type,a[i]);
a.splice(i,1);//移除时间之后一定要把这个方法从数组里移除了,要不然下一次就不能再绑定了。
return;//以为每一次绑定都是唯一的(bind上讲到过),所以移除后直接结束这个函数的运行就可以了;
}
}
因为用splice会造成“数组塌陷”,所以在正式的代码里,用a[i]=null来解决的,
完整的unbind代码如下:
function unbind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener(type,handler,false);
}else if(ele.detachEvent){
var a=ele['aBind'+type];
if(a){
for(var i=0;i<a.length;i++){
if(a[i].photo==handler){ele.detachEvent('on'+type,a[i]);a[i] =null;return;
}
}
}
}