美文网首页
js中的事件(event)

js中的事件(event)

作者: 张松1366 | 来源:发表于2016-07-19 18:45 被阅读0次

    什么是事件:


    我们可以简单的把事件理解为浏览器的感知系统。比如说:他可以感觉到用户是否点击(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;

    }

    }

    }

    }

    相关文章

      网友评论

          本文标题:js中的事件(event)

          本文链接:https://www.haomeiwen.com/subject/jzymjttx.html