• javascript中的事件


      

    作者:小土豆biubiubiu

    博客园:www.cnblogs.com/HouJiao/

    掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

    简书:https://www.jianshu.com/u/cb1c3884e6d5

    微信公众号:土豆妈的碎碎念(扫码关注,一起吸猫,一起听故事,一起学习前端技术)

    码字不易,点赞鼓励哟~

    一.简介

      javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作。

      本篇文章会从以下几个点去详细介绍javascript中的事件:

        >> 事件处理程序

        >> 事件流机制

        >> 事件对象

        >> 实践应用——事件委托

      

    二.事件处理程序

      事件处理程序指的是处理事件的函数,也可以称其为事件侦听器。

      所以了解事件的第一步就是如何去添加或者删除事件处理程序,下面会总结几种常见的事件处理程序的添加/删除方法。

    1.HTML事件处理程序

      HTML事件处理程序,即直接将事件处理程序添加在HTML元素中。

      一般在代码中比较常见,直接看示例。

    <button onclick="submit()">提交</button>
    <script type="text/javascript">
      function submit(){
        //事件处理逻辑
      }
    </script>

      这种直接在HTML中指定事件处理程序会有一个比较明显的缺点:有可能存在用户在点击按钮时,事件处理函数submit还未解析到,此时就会报错。

       

    2.DOM0级事件处理程序

      DOM0级规定的事件处理程序是在javascript中通过给事件处理程序定义一个函数实现的。

    <button id="submit">提交</button>
    <script type="text/javascript">
      var submitEle = document.getElementById('submit');
      submitEle.onclick = function submit(){
        //事件处理逻辑
      }
    </script>

      这种方式可以为button元素添加多个事件处理函数,只不过只有最后一个会生效。

      移除事件的方式也比较简单,即将事件处理程序的值设置为null。

    <button id="submit">提交</button>
    <script type="text/javascript">
      var submitEle = document.getElementById('submit');
      submitEle.onclick = function submit(){
        //事件处理逻辑
      }
      // 移除事件
      submitELe.onclick = null;
    </script>

      

    3.DOM2级事件处理程序

      DOM2级事件中为处理事件新增了两个api,一个是添加事件api:addEventListenter;一个是移除事件api:removeEventListener。

      这个两个api都可以接收三个参数:

        eventName      事件名称

        fn           事件处理函数

        booleanVal       一个布尔值,默认为false。用于指示事件处理函数在那个阶段执行(这个参数的作用会在事件流机制中说明)。

      

      api使用示例:

    <button id="submit">提交</button>
    <script type="text/javascript">
        var submitEle = document.getElementById('submit');
        function submit(){
            //事件处理逻辑
        }
        // 添加事件
        submitEle.addEventListener('click',submit);
       
        // 移除事件
        submitEle.removeEventListener('click',submit);
        
    </script>

      关于这两个api的使用,我们需要注意几点。

      首先第一点就是使用addEventListener可以添加多个事件处理程序,而且事件处理程序会按照代码添加顺序依次执行。

      第二点是使用addEventListener添加的事件处理程序,只能通过removeEventListener来移除。

      第三个需要注意的是使用addEventListener添加事件处理程序时,如果传递的第二个参数fn是一个匿名函数,则无法使用removeEventListener去移除。

      对于第三个点中提及到的匿名函数,这个写一个简单的示例。

    <button id="submit">提交</button>
    <script type="text/javascript">
        var submitEle = document.getElementById('submit');
       
        // 添加事件
        submitEle.addEventListener('click',function(){
           console.log("这是一个匿名函数");
         
        });
       
       // 移除事件:这种情况下使用removeEventListener是无法移除前面添加的事件处理程序
       submitEle.removeEventListener('click',function(){
    
       });
        
    </script>

      

    4.IE事件处理程序

         对于IE8以及更早版本的IE浏览器来说,它不支持addEventListener、removeEventListener这两个api。

      它支持和这两个类似的api分别为:attachEvent和deleteEvent。

      这两个api也需要接收两个参数:

        eventName     事件名称

        fn                    事件处理函数

      那话不多说,写一个示例:

    <button id="submit">提交</button>
    <script type="text/javascript">
        var submitEle = document.getElementById('submit');
        function submit(){
            //事件处理逻辑
        }
        // 添加事件
        submitEle.attachEvent('onclick',submit);
       
        // 移除事件
        submitEle.deleteEvent('onclick',submit);
        
    </script>

      使用这两个api的时候也有几个注意的地方。

      第一点是api的eventName参数传递,注意是onclick而不是click

      第二点就是使用attachEvent可以添加多个事件处理程序,而且事件处理程序会按照代码添加的相反顺序依次执行

      第三点是使用attachEvent添加的事件处理程序,只能通过deleteEvent来移除。

      第四个需要注意的是使用attachEvent添加事件处理程序时,如果传递的第二个参数fn是一个匿名函数,则无法使用deleteEvent去移除。

      其中最后两点和addEventListener/removeEventListener这两个api用法一致。

      和addEventListener/removeEventListener这两个api用法有区别的就是第一点和第二点,已经用红色标记出来,使用的时候需要注意。

         注意:IE11目前已经不支持attachEvent和deleteEvent,直接在脚本中调用方法会报错。

    5.通用事件处理程序

      前面介绍了多种不同添加/移除事件处理程序的方式。

      考虑到浏览器的兼容性,我们需要有一个通用的事件处理程序,以便我们进行快速的项目开发。

      因此,下面就写一个简单的通用事件处理程序。 

    var EventUtils = {
        //添加事件
        addEvent: function(element, eventName, fn){
            if(element.addEventListener){
                element.addEventListener(eventName, fn, false);
            }else if(element.attachEvent){
                element.attachEvent('on' + eventName, fn)
            }else{
                element.eventName = fn;
            }
        },
    
        //移除事件
        removeEvent: function(element, eventName, fn){
            if(element.removeEventListener){
                element.removeEventListener(eventName, fn, false);
            }else if (element.deleteEvent){
                element.deleteEvent('on' + eventName, fn);
            }else{
                element.eventName = null;
            }
        }
    };

      (这个事件处理程序比较简单,后面内容总结完成后会将这段代码进行补充完善。)

    三.事件流机制

      对于事件流专业的解释为:页面接收事件的顺序。

      那对于页面接收事件的顺序,分为两种,分别是:事件冒泡(event bubbling)和事件捕获(event capturing)。

    1.事件冒泡

      事件冒泡最早是IE提出来的事件流顺序。那么它指的是事件从子元素向父元素传播。

      现在我们有这样一段html文档:

      

      当我们点击触发button元素的click事件后,会依次触发button元素、p元素、div元素、body元素和html元素的click事件。

      

     2.事件捕获

      事件捕获和事件冒泡是相反的顺序,即事件由父元素向子元素传播。

      那还是前面的那段html文档

      

      对于捕获型的事件流,当我们触发了button的click事件后,会依次触发html元素、body元素、div元素、p元素和button元素的clcik事件。

      

       

      前面DOM2级的事件处理程序中,我们介绍了两个api:addEventListener和removeEventListener。

      这两个api的接收的第三个boolean参数就是用来指定事件处理程序在那个阶段执行。

      其中false值,即指定事件在冒泡阶段执行;相反指定true值,表示事件在捕获阶段执行。

      而对于IE提供的两个api:attachEvent和deleteEvent,它们并不支持第三个参数,因此IE8以前的版本只支持事件冒泡的机制。

      

    四.事件对象

      当我们触发html文档中元素的某个事件时,事件处理程序的内部就会有一个事件对象event产生,这个对象会包含和事件相关的一些信息。

      (下面说的事件对象仅限于DOM0级和DOM2级事件处理程序,IE会单独说明)

    属性/方法 类型 说明
    event.type String

    被触发事件的类型。

    比如触发button的click事件,那event.type的值就为"click"。

    event.target Element

    本次事件中的目标元素。

    因为事件流机制的存在,当点击button时,会按照不同的顺序触发其他元素的事件,在这个过程中,被点击的button元素就是事件中的目标元素。

    event.currentTarget Element

    本次事件中,当前正在处理的元素。

    按照事件冒泡或者捕获的顺序处理到那个元素的事件,那个元素就是当前正在处理的元素。

    event.cancelable Boolean

    表示是否可以取消事件的默认行为。

    true:表示可以取消事件的默认行为。

    event.preventDefault() Function

    调用该方法可以取消事件的默认行为,但是前提是event.cancelable的值为true。

    event.bubbles Boolean

    表明事件是否冒泡

    event.stopPropagation() Function

    调用该方法可以取消事件的下一步冒泡,但前提是event.bubbles的值为true。

     

       关于事件对象event的信息,我们一个一个来实践一下。

    1.event.type

    <button id='btn'>点击</button>
    <script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        //事件处理程序
        console.log(event.type);
    }
    </script>

      

    2.event.target和event.currentTarget

      

    <body>
        <div id='box'>
            <p id='pbox'>
                <button id='btn'>点击</button>
            </p>
        </div>
    </body>
    <script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        //事件处理程序
        console.log("btn click");
        console.log("event.target:");
        console.log(event.target);
        console.log("event.currentTarget");
        console.log(event.currentTarget);
    }
    
    var box = document.getElementById('box');
    box.onclick = function(){
        //事件处理程序
        console.log("box click")
        console.log("event.target:");
        console.log(event.target);
        console.log("event.currentTarget");
        console.log(event.currentTarget);
    }
    </script>

      

       

      

      可以看到使用DOM0级方式添加的事件处理程序,默认的事件流机制是事件冒泡。

      即事件触发顺序有子元素button传播到父元素div。

      那这个操作中,我们点击的目标元素是button,因此event.target一直是button元素。

      而event.currentTarget随着事件触发的顺序一直在变化,触发到那个元素的事件event.currentTarget就是那个元素。

    3.event.cancelable和event.preventDefault

      前面我们说在event.cancelable为true的前提下,可以调用event.preventDefault来阻止事件的默认行为。

      那什么是事件的默认行为呢?

      比如:

        浏览器中单击右键,出现浏览器默认的菜单栏选项;

        点击a标签会发生跳转行为;

        form表单中点击提交按钮会提交表单数据;

      接下来写一个示例。

    <a id='link' href='https://www.baidu.com'>跳转</a>
    <script>
    var link = document.getElementById('link');
    link.onclick = function(){
        //事件处理程序
        console.log("link click");
        console.log(event.cancelable);
        event.preventDefault();
    }
    </script>

      在这个示例中,正常情况下点击"跳转"会跳转到百度首页,但是我们在a标签的onclick事件中调用了event.preventDefault()方法阻止了a标签默认的跳转行为。

      所以我们点击"跳转"后,只会在控制台打印"link click"和event.cancelable的值,页面并不会发生跳转行为。

      

     4.event.bubbles和event.Propagation()

      前面第2小节中关于event.target和event.currentTarget的示例,因为默认的事件冒泡机制,导致click事件从button元素传播到了父元素div

      那现在我们在事件处理程序中调用event.Propagation方法就可以阻止事件冒泡。

    <body>
        <div id='box'>
            <p id='pbox'>
                <button id='btn'>点击</button>
            </p>
        </div>
    </body>
    <script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        //事件处理程序
        console.log("btn click");
        console.log(event.bubbles);
        event.stopPropagation();
    }
    
    var box = document.getElementById('box');
    box.onclick = function(){
        //事件处理程序
        console.log("box click")
    }
    </script>

      

      

      从结果可以看到,调用event.stopPropagation()后,父元素的click方法已经成功被阻止。

      

      那这里事件对象的信息基本就总结完了,上面的示例均使用DOM0级的事件处理程序实现的,那实际上对于DOM2级的事件处理程序也是适用的。

      所以DOM2事件处理程序就不写在示例。 

    5.IE中的事件对象

      IE中的事件对象和DOM0、DOM2中的事件对象还是有很大区别的。

      它包含的属性和方法如下:

    属性/方法 类型 说明
    type String 被触发事件的类型
    cancelBubble Boolean

    设置事件是否冒泡。

    默认值为false,将其设置为true就可以取消事件冒泡。

    returnValue Boolean

    设置事件的默认行为。

    默认值为true,将其设置为false就可以取消事件的默认行为。

    srcElement Element

    本次事件中的目标元素。

    (同DOM1、DOM2级中的target)

      IE事件对象的这些属性和方法就不代码演示了,也比较简单。

      只是IE中使用事件对象时,需要注意下面的几点。

      (下面示例的演示,不同版本的结果使用的是IE浏览器自带的仿真工具进行的)

      首先第一点是使用DOM0级添加事件处理程序时,事件处理程序内部的事件对象event是作为window的一个属性存在的。

    <button id='btn'>点击</button>
    <script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        alert(window.event == event);
        // 单独打印window.event和event,结果均是同一个对象
        // 但是只有在IE11中alert出来的结果才是true,其他版本alert均为false
    }
    </script>
         

      第二点就是在IE事件处理程序内部的this不一定等于目标元素,因此在事件处理程序内部,最好用event.srcElement来代替this。

      先测试一下DOM0级事件处理程序的结果。

    <button id='btn'>点击</button>
    <script>
    var btn = document.getElementById('btn');
    btn.onclick = function(){
        alert(this); 
        // IE8-IE11 打印 [object HTMLButtonElement]
        alert(event.srcElement);  
        // IE8-IE11 打印 [object HTMLButtonElement]
    }

      再测试一下attachEvent和deleteEvent这两个api(IE11已经不支持这两个api了,前面已经提过)

    <button id='btn'>点击</button>
    <script>
    var btn = document.getElementById('btn');
    btn.attachEvent('onclick',function(){
        alert(this);
        // IE8-IE10 结果为 [object Window]
        alert(event.srcElement);
        // IE8-IE10 结果为 [object HTMLButtonElement]
    })
    </script>

      

      关于事件对象这里就介绍完了,一整个关于IE浏览器的测试代码写下来还是有些难受。

      到这里,有关javascript中的事件内容基本总结完了,这里呢,我们需要把前面那个通用的事件处理程序完善一下。

    <script>
        var EventUtils = {
            // 添加事件
            addEvent: function(element, eventName, fn){
                if(element.addEventListener){
                    element.addEventListener(eventName, fn, false);
                }else if(element.attachEvent){
                    element.attachEvent('on' + eventName, fn)
                }else{
                    element.eventName = fn;
                }
            },
    
            // 移除事件
            removeEvent: function(element, eventName, fn){
                if(element.removeEventListener){
                    element.removeEventListener(eventName, fn, false);
                }else if (element.deleteEvent){
                    element.deleteEvent('on' + eventName, fn);
                }else{
                    element.eventName = null;
                }
            },
    
            // 获取事件对象
            getEvent: function(event){
                return event?event:window.event;
            },
    
            // 获取事件类型
            getEventType: function(event){
                return event.type;
            },
    
            // 获取被执行事件的目标元素
            getEventTarget: function(event){
                return event.target | event.srcElement;
            },
    
            // 禁用元素的默认行为
            preventDefault: function(event){
                if(event.preventDefault){
                    event.preventDefault();
                }else{ //IE8以及更低版本
                    event.returnValue = false;
                }
            },
    
            // 阻止元素冒泡
            stopPropagation: function(event){
                if(event.stopPropagation){                    
                    event.stopPropagation();
                }else{ //IE8以及更s低版本
                    event.cancelable = true;
                }
            }
        }; 
    </script>

    五.实践应用——事件委托

      最后这一部分,主要是针对事件冒泡的一个应用。

      我们假设有这样一个场景:有一个商品列表,点击其中某一个商品,弹框显示商品标题。同时用户可以在商品列表添加商品。

      现在我们实现一下这个需求。

    <html>
        <head>
            <meta charset="utf-8"/>
            <title>事件委托</title>
            <style>
                li{
                    cursor: pointer;
                    padding: 20px;
                }
            </style>
        </head>
        <body>
            <h1>事件委托</h1>
            <div class='box'>
                <button onclick="add()">添加商品</button>
                <div class='list'>
                    <h3>商品列表</h3>
                    <ul>
                        <li>商品1</li>
                        <li>商品2</li>
                        <li>商品3</li>
                        <li>商品4</li>
                    </ul>
                </div>
            </div>
            <script type="text/javascript">
                // 遍历商品列表添加点击事件
                var liEle = document.getElementsByTagName('li');
                for(var i = 0; i<liEle.length; i++){
                    var element = liEle[i];
                    element.addEventListener('click', function(){
                        alert(this.innerHTML);
                    })
                }
    
                // 添加商品
                function add(){
                    var ulEle = document.getElementsByTagName('ul')[0];
                    var liLength = document.getElementsByTagName('li').length;
                    var newLiEle = document.createElement('li');
                    newLiEle.innerHTML = "商品"+(liLength+1);
                    ulEle.appendChild(newLiEle);
                }
            </script>
        </body>
    </html>

      我们看一下浏览器效果。

      

      可以看到,循环li元素添加的点击事件均可以正常执行,而点击【添加商品】按钮添加的商品5,点击之后并没有click事件,因此没有弹窗显示。

      这个需求实际上可以转化为这样的应用场景:事件添加个数不确定。

      那么根据前面提说的事件流、事件冒泡和事件对象,我们很容易就能想到解决办法:

        将click事件添加到父元素ul元素上,利用事件冒泡和event.target实现弹窗。

      这个办法我们被称为事件委托机制。

      下面我们代码实现一下。

      (修改代码:27行-32行)

     1 <html>
     2     <head>
     3         <meta charset="utf-8"/>
     4         <title>事件委托</title>
     5         <style>
     6             li{
     7                 cursor: pointer;
     8                 padding: 20px;
     9             }
    10         </style>
    11     </head>
    12     <body>
    13         <h1>事件委托</h1>
    14         <div class='box'>
    15             <button onclick="add()">添加商品</button>
    16             <div class='list'>
    17                 <h3>商品列表</h3>
    18                 <ul>
    19                     <li>商品1</li>
    20                     <li>商品2</li>
    21                     <li>商品3</li>
    22                     <li>商品4</li>
    23                 </ul>
    24             </div>
    25         </div>
    26         <script type="text/javascript">
    27             // 将click事件添加到父元素ul元素上,利用事件冒泡和event.target实现弹窗
    28             var ulEle = document.getElementsByTagName('ul')[0];
    29             ulEle.addEventListener('click', function(){
    30                 var target = event.target;
    31                 alert(target.innerHTML);
    32             })
    33            
    34             // 添加商品
    35             function add(){
    36                 var ulEle = document.getElementsByTagName('ul')[0];
    37                 var liLength = document.getElementsByTagName('li').length;
    38                 var newLiEle = document.createElement('li');
    39                 newLiEle.innerHTML = "商品"+(liLength+1);
    40                 ulEle.appendChild(newLiEle);
    41             }
    42         </script>
    43     </body>
    44 </html>

      

      

      

      完结!!!

     

       

    作者:小土豆biubiubiu

    博客园:www.cnblogs.com/HouJiao/

    掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

    简书:https://www.jianshu.com/u/cb1c3884e6d5

    微信公众号:土豆妈的碎碎念(扫码关注,一起吸猫,一起听故事,一起学习前端技术)

    码字不易,点赞鼓励哟~

    备注:转载请注明出处

  • 相关阅读:
    深入研究Servlet线程安全性问题
    Sun公司java语言编码规范
    JAVA的内省机制(introspector)与反射机制(reflection)[转]
    Oracle的悲观锁和乐观锁
    java中120个经典问题
    自定义Java异常
    Java事务处理总结
    Tomcat内存溢出的三种情况及解决办法分析
    .net基础
    C#.Net中的转义
  • 原文地址:https://www.cnblogs.com/HouJiao/p/12309348.html
Copyright © 2020-2023  润新知