• (转)JavaScript事件---事件绑定和深入


    内容提纲:

    1.传统事件绑定的问题

    2.W3C事件处理函数

    3.IE事件处理函数

    4.事件对象的其他内容

     

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

     

    一.传统事件绑定的问题

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

     

    复制代码
    1  var box = document.getElementById('box');        //获取元素
    2 
    3        box.onclick = function () {                //元素点击触发事件
    4 
    5               alert('Lee');
    6 
    7        };
    复制代码

     

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

    window.onload = function () {                           //第一组程序项目或第一个JS文件

           alert('Lee');

    };

    window.onload = function () {                           //第二组程序项目或第二个JS文件

           alert('Mr.Lee');

    };

    当两组程序或两个JS文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致前面的window.onload完全失效了。

     

    解决覆盖问题,我们可以这样去解决:

    PS如果一开始没有window.onload,旧版火狐显示undefined,新版显示object,谷歌和IE浏览器也是object;如果有window.onload,所有浏览器都会显示function。 

    复制代码
     1 window.onload = function () {                           //第一个要执行的事件,会被覆盖
     2 
     3        alert('Lee');
     4 
     5 };
     6 
     7 if (typeof window.onload == 'function') {             //判断之前是否有window.onload
     8 
     9        var saved = null;                                //创建一个保存器
    10 
    11        saved = window.onload;                           //把之前的window.onload保存起来
    12 
    13 }
    14 
    15 window.onload = function () {                           //最终一个要执行事件
    16 
    17        if (saved) saved();                              //执行之前一个事件
    18 
    19        alert('Mr.Lee');                                 //执行本事件的代码
    20 
    21 };
    复制代码

    问题二:事件切换器

    window.onload = function () {

           var box = document.getElementById('box');

           box.onclick =toBlue;                    //第一次执行toBlue()

    };

    function toRed() {

           this.className = 'red';

           this.onclick = toBlue;                                 //第三次执行toBlue(),然后来回切换

    }

    PS:直接将函数绑定给事件处理函数,toBlue里面的this就是事件对象box,否则是window!(事件入门一篇提到过)

    function toBlue() {

           this.className = 'blue';

           this.onclick = toRed;                                  //第二次执行toRed()

    }

     

    这个切换器在扩展的时候,会出现一些问题:

    1.如果增加一个执行函数,那么会被覆盖

           box.onclick = toAlert;                                       //被增加的函数

           box.onclick = toBlue;                                      //toAlert被toBlue覆盖了

     

    2.如果解决覆盖问题,就必须包含到一起,然后同时执行,但又出新问题

          

    box.onclick = function () {                                //包含进去,但可读性降低

     

                  //第一次不会被覆盖,但第二次又被覆盖(被toBlue里面的onclick覆盖)

     

         toAlert();                                                 

     

                  toBlue.call(this);                                      //还必须把this传递到切换器里

     

           };

     

    PS:通过匿名函数执行一个函数,此函数里面的this是window,所以必须在匿名函数中把this传递过去!

     

    综上的三个问题:覆盖问题、可读性问题、this传递问题。我们来创建一个自定义的事件处理函数,来解决以上三个问题。

     

    //添加事件函数

    //obj相当于window

    //type相当于onload

    //fn相当于function () {}

    //组合起来:window.onload = function(){}

    复制代码
     1 function addEvent(obj, type, fn) {                             //取代传统事件处理函数
     2 
     3        var saved = null;                                       //保存每次触发的事件处理函数
     4 
     5        if (typeof obj['on' + type] == 'function') {         //判断是不是事件
     6 
     7               saved = obj['on' + type];                        //如果有,保存起来
     8 
     9        }
    10 
    11        obj['on' + type] = function () {                  //然后执行window.onload相当于window['onload'];      
    12 
    13         if (saved) saved();                               //执行上一个 
    14 
    15               fn.call(this);                               //执行函数,把this传递过去
    16 
    17        };
    18 
    19 }
    20
    23 addEvent(window, 'load', function () {                //执行到了
    24 
    25        alert('Lee');
    26 
    27 });
    28 
    29 addEvent(window, 'load', function () {                //执行到了
    30 
    31        alert('Mr.Lee');
    32 
    33 });
    复制代码

     

    PS:以上编写的自定义事件处理函数,还有一个问题没有处理,就是两个相同函数名的函数误注册了两次或多次,那么应该把多余的屏蔽掉。那我们就需要把事件处理函数进行遍历,如果有同样名称的函数名就不添加即可。(这里就不做了)

    addEvent(window, 'load', init);                          //注册第一次

    addEvent(window, 'load', init);                          //注册第二次,应该忽略

    function init() {

           alert('Lee');

    }

     

    用自定义事件函数注册到切换器上查看效果:

    复制代码
     1 addEvent(window, 'load', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5 addEvent(box, 'click', function () {       //增加一个执行函数,每次都执行,不会被覆盖
     6 
     7               alert('Mr.Lee');
     8 
     9        });
    10 
    11        addEvent(box, 'click', toBlue);
    12 
    13 });
    14 
    15  
    16 
    17 function toRed() {
    18 
    19        this.className = 'red';
    20 
    21        addEvent(this, 'click', toBlue);
    22 
    23 }
    24 
    25  
    26 
    27 function toBlue() {
    28 
    29        this.className = 'blue';
    30 
    31        addEvent(this, 'click', toRed);
    32 
    33 }
    复制代码

    PS:当你单击很多很多次切换后,浏览器直接卡死,或者弹出一个错误:too much recursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的移除,导致越积越多,最后卡死(解决方案就是用完的事件及时的移除掉)。

    //删除事件函数

    1 function removeEvent(obj, type) {
    2 
    3        if (obj['on'] + type) obj['on' + type] = null;         //删除事件处理函数
    4 
    5 }

    以上的删除事件处理函数只不过是一刀切的删除了,这样虽然解决了卡死和太多递归的问题。但其他的事件处理函数也一并被删除了,导致最后得不到自己想要的结果。如果想要只删除指定的函数中的事件处理函数,那就需要遍历,查找。(这里就不做了,提示:在上面的删除函数中加上第三个参数fn)

     

     

     

    二.W3C事件处理函数

    “DOM2级事件”定义了两个方法,用于添加事件和删除事件处理程序的操作:addEventListener()和removeEventListener()所有DOM节点中都包含这两个方法,并且它们都接受3个参数;事件名、函数、冒泡或捕获的布尔值(true表示捕获,false表示冒泡)

    //问题1:覆盖问题(解决)

    复制代码
     1 window.addEventListener('load', function () {
     2 
     3        alert('Lee');
     4 
     5 }, false);
     6 
     7  
     8 window.addEventListener('load', function () {
     9 
    10        alert('Mr.Lee');
    11 
    12 }, false);
    复制代码

    //问题2:屏蔽掉相同函数问题(解决)

    复制代码
    1 window.addEventListener('load', init, false);        //第一次执行了
    2 
    3 window.addEventListener('load', init, false);        //第二次被屏蔽了
    4 
    5 function init() {
    6 
    7        alert('Lee');
    8 
    9 }
    复制代码

    //问题3:是否传递了this

    复制代码
     1 window.addEventListener('load', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5        box.addEventListener('click', function(){
     6 
     7             alert(this);     //这儿的this是box
     8     
     9         }, false);
    10 
    11 },false);            
    复制代码

    //修改事件切换器

    复制代码
     1 window.addEventListener('load', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5        box.addEventListener('click', toBlue, false);
     6 
     7 },false);
     8 
     9 
    10 function toRed() {
    11 
    12        this.className = 'red';
    13 
    14        this.removeEventListener('click', toRed, false);
    15 
    16        this.addEventListener('click', toBlue, false);
    17 
    18 }
    19 
    20 
    21 function toBlue() {
    22 
    23        this.className = 'blue';
    24 
    25        this.removeEventListener('click', toBlue, false);
    26 
    27        this.addEventListener('click', toRed, false);
    28 
    29 }
    复制代码

    //问题4:添加一个额外的方法会被覆盖或者只能执行一次(解决)

    复制代码
     1 window.addEventListener('load', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5         box.addEventListener('click', function () {          //不会被误删
     6 
     7             alert('Mr.Lee');
     8 
     9         }, false);
    10 
    11        box.addEventListener('click', toBlue, false);        //引入切换也不会因太多递归卡死
    12 
    13 }, false);
    14 
    15 
    16 function toRed() {
    17 
    18        this.className = 'red';
    19 
    20        this.removeEventListener('click', toRed, false);
    21 
    22        this.addEventListener('click', toBlue, false);
    23 
    24 }
    25 
    26  
    27 function toBlue() {
    28 
    29        this.className = 'blue';
    30 
    31        this.removeEventListener('click', toBlue, false);
    32 
    33        this.addEventListener('click', toRed, false);
    34 
    35 }        
    复制代码

    综上所述:W3C是比较完美的解决了这些问题,非常好用;但是IE8和之前的浏览器并不支持,而是采用了自己的事件,当然,IE9已经完全支持!

     

    设置冒泡和捕获阶段

    在事件入门一篇中介绍了事件冒泡,即从里到外触发。我们也可以通过event对象来阻止某一阶段的冒泡。

    W3C现代事件绑定可以设置冒泡和捕获。把最后的布尔值设置成true,则为捕获;设置成false,则为冒泡

    //设置为true,捕获

           document.addEventListener('click', function () {

                  alert('document');

           }, true);                                                                 

      box.addEventListener('click', function () {

                  alert('div');

           }, true);                                                          

     

     

     

     

    三.IE事件处理函数

    IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的参数:事件名称和函数。

    在使用这两组函数的时候,与W3C区别如下:

    1.IE不支持捕获,只支持冒泡;

    2.IE添加事件不能屏蔽重复的函数;

    3.IE中的this指向的是window而不是DOM对象;

    4.在传统事件上,IE是无法接受到event对象的,但使用了attchEvent()却可以,但有些区别。

     

    //问题1:覆盖问题(解决,但有不同,输出顺序相反)

    复制代码
     1 window.attachEvent('onload', function () {
     2 
     3        alert('Lee');
     4 
     5 });
     6 
     7 window.attachEvent('onload', function () {
     8 
     9        alert('Mr.Lee');
    10 
    11 });
    12 
    13 window.attachEvent('onload', function () {
    14 
    15        alert('Miss.Lee');
    16 
    17 });
    复制代码

     

    //问题2:相同函数屏蔽的问题(未解决)

    复制代码
     1 window.attachEvent('onload', init);
     2 
     3 window.attachEvent('onload', init);
     4 
     5  
     6 function init() {
     7 
     8        alert('Mr.Lee');
     9 
    10 }
    复制代码

     

    //问题3:不可以传递this(未解决)

    复制代码
     1 window.attachEvent('onload', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5        box.attachEvent('onclick', function () {
     6 
     7               //alert(this === box);                   //false
     8 
     9               alert(this === window);                    //不能传递this
    10 
    11        });
    12 
    13 });
    14 
    15 //可以通过call()传递过去
    16 
    17 window.attachEvent('onload', function () {
    18 
    19        var box = document.getElementById('box');
    20 
    21        box.attachEvent('onclick', function () {       
    22 
    23               toBlue.call(box);                 //把this直接call过去(但切换器我们不这样做!)
    24 
    25        });
    26 
    27 });
    28 
    29  
    复制代码

    //问题4:添加一个额外的方法会被覆盖或者只能执行一次(解决)

    复制代码
    window.attachEvent('onload', function () {
    
           var box = document.getElementById('box');
    
           box.attachEvent('onclick', function () {
    
                  alert('Lee');
    
           });
    
           box.attachEvent('onclick', function () {
    
                  alert('Mr.Lee');
    
           });
    
    });
    复制代码

    在传统绑定上,IE是无法像W3C那样通过传参接受event对象,但如果使用了attachEvent()却可以。

    复制代码
     1 window.attachEvent('onload', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5        //box.onclick = function (evt) {                     //传统方法IE无法通过参数获取evt
     6 
     7        //     alert(evt);
     8 
     9        //}
    10 
    11        box.attachEvent('onclick', function (evt) {           //IE的现代事件绑定机制是可以的
    12 
    13               //alert(evt);                               //object
    14 
    15               //alert(evt.type);                                        //click    
    16 
    17               //alert(evt.srcElement.tagName);            //box, 这个可以有
    18 
    19               alert(window.event.srcElement.tagName);    //box, 这个更可以有
    20 
    21        });
    22 
    23 });
    复制代码

     

    //IE事件切换器

    复制代码
     1 window.attachEvent('onload', function () {
     2 
     3        var box = document.getElementById('box');
     4 
     5        box.attachEvent('onclick', toBlue);
     6 
     7 });
     8 
     9  
    10 function toRed() {
    11 
    12        var that = window.event.srcElement;
    13 
    14        that.className = 'red';
    15 
    16        that.detachEvent('onclick', toRed);
    17 
    18        that.attachEvent('onclick', toBlue);
    19 
    20 }
    21 
    22  
    23 function toBlue() {
    24 
    25        var that = window.event.srcElement;
    26 
    27        that.className = 'blue';
    28 
    29        that.detachEvent('onclick', toBlue);
    30 
    31        that.attachEvent('onclick', toRed);
    32 
    33 }
    复制代码

    最后,为了让IE和W3C可以兼容这个事件切换器,我们写成如下方式:

    复制代码
     1 function addEvent(obj, type, fn) {                            //添加事件兼容
     2 
     3        if (obj.addEventListener) {
     4 
     5               obj.addEventListener(type, fn);
     6 
     7        } else if (obj.attachEvent) {
     8 
     9               obj.attachEvent('on' + type, fn);
    10 
    11        }
    12 
    13 }
    14 
    15  
    16 
    17 function removeEvent(obj, type, fn) {                      //移除事件兼容
    18 
    19        if (obj.removeEventListener) {
    20 
    21               obj.removeEventListener(type, fn);
    22 
    23        } else if (obj.detachEvent) {
    24 
    25               obj.detachEvent('on' + type, fn);
    26 
    27        }
    28 
    29 }
    30 
    31  
    32 
    33 function getTarget(evt) {                                       //得到事件目标
    34 
    35        if (evt.target) {
    36 
    37               return evt.target;
    38 
    39        } else if (window.event.srcElement) {
    40 
    41               return window.event.srcElement;
    42 
    43        }
    44 
    45 }
    46 
    47  
    48 
    49 addEvent(window, 'load', function () {
    50 
    51        var box = document.getElementById('box');
    52 
    53        addEvent(box, 'click', toBlue);
    54 
    55 });
    56 
    57  
    58 
    59 function toRed(evt) {
    60 
    61        var that = getTarget(evt);
    62 
    63        that.className = 'red';
    64 
    65        removeEvent(that, 'click', toRed);
    66 
    67        addEvent(that, 'click', toBlue);
    68 
    69 }
    70 
    71  
    72 
    73 function toBlue(evt) {
    74 
    75        var that = getTarget(evt);
    76 
    77        that.className = 'blue';
    78 
    79        removeEvent(that, 'click', toBlue);
    80 
    81        addEvent(that, 'click', toRed);
    82 
    83 }
    84 
    85  
    复制代码

    PS:调用忽略,IE兼容的事件,如果要传递this,改成call即可(上面问题3部分说过了,但是一般不使用这种形式)。

    PS:IE中的事件绑定函数attachEvent()和detachEvent()可能在实践中不去使用,有几个原因:

    1.IE9就将全面支持W3C中的事件绑定函数;

    2.IE的事件绑定函数无法传递this;

    3.IE的事件绑定函数不支持捕获;

    4.同一个函数注册绑定后,没有屏蔽掉;

    5.有内存泄漏的问题。

    至于怎么替代,这儿暂时不做探讨···

     

     

     

     

    四.事件对象的其他内容

    1.获取移入移出对象

    W3C提供了一个属性:relatedTarget;这个属性可以在mouseover和mouseout事件中获取从哪里移入和从哪里移出的DOM对象。

           box.onmouseover = function (evt) {                     //鼠标移入box

                  alert(evt.relatedTarget);                              //获取移入box最近的那个元素对象

           }                                                                   

     

      box.onmouseout = function (evt) {                         //鼠标移出box

                  alert(evt.relatedTarget);                              //获取移出box最近的那个元素对象

           }                                                                   

     

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

           box.onmouseover = function (evt) {                   //鼠标移入box

                  alert(window.event.fromElement.tagName); //获取移入box最近的那个元素对象

           }

     

           box.onmouseout = function (evt) {                            //鼠标移入box

                  alert(window.event.toElement.tagName);     //获取移入box最近的那个元素对象

           }

     

    跨浏览器兼容:

    复制代码
     1 function getTarget(evt) {
     2 
     3        var e = evt || window.event;                        //得到事件对象
     4 
     5        if (e.srcElement) {                                    //如果支持srcElement,表示IE
     6 
     7               if (e.type == 'mouseover') {                   //如果是mouseover
     8 
     9                      return e.fromElement;                   //就使用fromElement
    10 
    11               } else if (e.type == 'mouseout') {              //如果是mouseout
    12 
    13                      return e.toElement;                      //就使用toElement
    14 
    15               }
    16 
    17        } else if (e.relatedTarget) {                           //如果支持relatedTarget,表示W3C
    18 
    19               return e.relatedTarget;
    20 
    21        }
    22 
    23 }
    复制代码

    2.阻止默认行为

    有时我们需要阻止事件的默认行为,比如:一个超链接的默认行为就点击然后跳转到指定的页面。那么阻止默认行为就可以屏蔽跳转的这种操作,而实现自定义操作。

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

           link.onclick = function () {

                  alert('Lee');

          return false;                                              //直接给个false,就不会跳转了。

           };

     

    PS:虽然return false;可以实现这个功能,但有漏洞:

    第一:必须写到最后,这样导致中间的代码执行后,有可能执行不到return false;

    第二:return false写到最前那么之后的自定义操作就失效了。

    所以,最好的方法应该是在最前面就阻止默认行为,并且后面还能执行代码。

         

    复制代码
     1   link.onclick = function (evt) {
     2 
     3               evt.preventDefault();                               //W3C阻止默认行为,放哪里都可以
     4 
     5               alert('Mr.Lee');
     6 
     7        };
     8 
     9  
    10 
    11        link.onclick = function (evt) {
    12 
    13               window.event.returnValue = false;                  //IE阻止默认行为
    14 
    15               alert('Mr.Lee');
    16 
    17        };
    18 
    19  
    复制代码

     

     

    跨浏览器兼容:

    复制代码
     1 function preDef(evt) {
     2 
     3        var e = evt || window.event;
     4 
     5        if (e.preventDefault) {
     6 
     7               e.preventDefault();
     8 
     9        } else {
    10 
    11               e.returnValue = false;
    12 
    13        }
    14 
    15 } 
    复制代码

     

    3.上下文菜单事件

    上下文菜单事件:contextmenu,当我们右击网页的时候,会自动出现windows自带的菜单。那么我们可以使用contextmenu事件来修改我们指定的菜单,前提是把右击的默认行为取消掉。

    小示例:

    html代码部分:

    复制代码
     1 <body>
     2 
     3 <textarea id="text" style="200px;height:100px;"></textarea>
     4 
     5 <ul id="menu">
     6 
     7               <li>菜单1</li>
     8 
     9               <li>菜单2</li>
    10 
    11               <li>菜单3</li>
    12 
    13 </ul>
    14 
    15 </body>
    复制代码

    css代码部分:

    复制代码
     1 #menu {
     2 
     3        50px;
     4 
     5        background:grey;
     6 
     7        position:absolute;
     8 
     9        display:none;
    10 
    11 }
    复制代码

    JS代码部分:

    复制代码
    addEvent(window, 'load', function () {
    
           var text = document.getElementById('text');
    
           addEvent(text, 'contextmenu', function (evt) {
    
                  preDef(evt);
    
                  var menu = document.getElementById('menu');
    
                  var e = evt || window.event;
    
                  menu.style.left = e.clientX + 'px';
    
                  menu.style.top = e.clientY + 'px';
    
                  menu.style.display = 'block';
    
                 
    
                  addEvent(document, 'click', function () {
    
                         menu.style.display = 'none';
    
                  });
    
           });
    
    });
    复制代码

    PS:contextmenu事件很常用,而且此事件各浏览器兼容性较为稳定。

     

    4.卸载前事件

    卸载前事件:beforeunload,这个事件可以帮助在离开本页的时候给出相应的提示,“离开”或者“返回”操作。

    addEvent(window, 'beforeunload', function (evt) {

           preDef(evt);    //必须要有,默认形式

    });

     

    5.鼠标滚轮事件

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

    复制代码
     1 addEvent(document, 'mousewheel', function (evt) {          //非火狐
     2 
     3        alert(getWD(evt));
     4 
     5 });
     6 
     7 addEvent(document, 'DOMMouseScroll', function (evt) {  //火狐
     8 
     9        alert(getWD(evt));
    10 
    11 });
    12 
    13  
    14 
    15 function getWD(evt) {
    16 
    17        var e = evt || window.event;
    18 
    19        if (e.wheelDelta) {
    20 
    21               return e.wheelDelta;
    22 
    23        } else if (e.detail) {
    24 
    25               return -evt.detail * 30;                        //保持计算的统一
    26 
    27        }
    28 
    29 }
    复制代码

    PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll。

     

    6.其他

    DOMContentLoaded事件和readystatechange事件(很重要),有关DOM加载方面的事件,关于这两个事件的内容非常多,这儿就暂时不聊了!

     

    for my lover and

    thank you Mr.Lee!

    原文路径:http://www.cnblogs.com/ttcc/p/3767820.html

  • 相关阅读:
    Sunday算法
    砝码称重 洛谷 1441
    树秀于林风必摧之——线段树
    常用stl(c++)
    Vue 根组件,局部,全局组件 | 组件间通信,案例组件化
    Win下JDK的安装和简单使用教程
    ubuntu服务器远程连接xshell,putty,xftp的简单使用教程
    ubuntu下安装pdo和pdo_mysql扩展
    服务器和域名的简单个人认知
    对大一一年的总结和对大二的规划
  • 原文地址:https://www.cnblogs.com/christal-11/p/5775814.html
Copyright © 2020-2023  润新知