zoukankan      html  css  js  c++  java
  • 彻底理解H5的DOM事件

    我们大家都知道,人与人之间的交流可以通过语言,文字,肢体动作,面部微表情等,但是你知道Javascript和HTML之间是通过什么进行交互的么?你又知道Javascript和HTML之间是如何进行交互的么?如果你不是那么清楚,可以看这篇文章。。。

    前面的话:

    这篇博文由浅入深,先介绍两种事件流,然后介绍常见的事件处理程序以及它们之间的差异(涉及到一个简单的兼容的处理函数),后面还会针对事件对象进行深入学习,最后则是通过DOM事件实现的小案例。

    事件流:

    关于事件流,简单的来说就是:事件流描述的是从页面中接受事件的顺序。下面是《Javascript高级程序设计》里面的说法:

    当浏览器发展到第四代时(IE4和Netscape Communicator 4),浏览器团队遇到一个很有意思的问题:页面的哪一部分会拥有特定的事件?想象下在一张纸上有一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是一组圆。两家公司的开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,那么同时你也单击了按钮的容器元素,甚至整个页面。

    事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了两个截然相反的事件流概念。IE的事件流是事件冒泡流,而Netscape的事件流是事件捕获流。

    事件冒泡

    IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。看下面的例子

    复制代码
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>事件流</title>
        </head>
        <body>
            <input type="button" id="btn" value="" />
        </body>
    </html>
    复制代码

    点击按钮,那么这个click事件会按照这样传播:
    <input-->body-->html-->document

    事件捕获

    Netscape团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件。

    针对上面同样的例子,点击按钮,那么此时click事件会按照这样传播:
    document-->html-->body-->input

    虽然事件捕获是Netscape唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持这种事件流模型。但由于老版本的浏览器不支持,因此很少有人使用事件捕获。

    事件处理程序

    HTML事件处理程序

    HTML事件处理程序(现在不建议使用了):事件直接加在HTML代码中,例如:

    <input type="button" id="btn" value="HTML事件处理程序" onclick="alert('HTML事件处理程序')" />

    或者:

    <input type="button" id="btn1" value="HTML事件处理程序" onclick="demo1()" />
    function demo1(){
        alert("HTML事件处理程序");
    }

    缺点:HTML和js代码高耦合,如果修改,就要修改两个地方--HTML元素内和script函数。

    DOM0级事件处理程序

    DOM0级事件处理程序,是一种较传统的方式:把一个函数赋值给一个事件的处理程序属性。例如:

    var btn2 = document.getElementById("btn2");
    btn2.onclick = function(){
        alert("DOM0级事件处理程序");
    }

    现在用得比较多,优势是简单(只需要考虑JS不考虑HTML)且跨浏览器。如果想取消点击事件,将其设为null即可。

    var btn2.onclick = null;//这时点击不会弹出窗口,表示已经取消了该点击事件

    下面是我学习时候的一些思考:

    思考一:DOM0级事件是否可以同时添加多个不同事件处理方法

    复制代码
    var btn2 = document.getElementById("btn2");
    btn2.onclick = function(){
        console.log("DOM0级事件处理程序-click-01");
    }
    btn2.onmouseover = function(){
        console.log("DOM0级事件处理程序-mouseover");
    }
    复制代码

    结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-01。
    说明:DOM0级事件可以同时添加多个不同事件处理方法

    思考二:DOM0级事件是否可以同时添加多个相同事件处理方法

    我们在前面的基础上添加如下代码:

    btn2.onclick = function(){
        console.log("DOM0级事件处理程序-click-02");
    }

    结果:鼠标经过,控制台出现:DOM0级事件处理程序-mouseover;鼠标点击,控制台出现:DOM0级事件处理程序-click-02。(在这里你可能以为会出现DOM0级事件处理程序-click-01和DOM0级事件处理程序-click-02)
    说明:DOM0级事件不可以同时添加多个相同事件处理方法(这样说好像不太严格,大概意思就是直接忽略了前面的onclick,只显示最后的onclick的结果。
    那么这里就设下一个疑问:我们应该如何让DOM同时绑定多个相同事件呢???我们接着往下看

    DOM2级事件处理程序

    DOM2级事件处理程序定义了两个方法--用于处理指定和删除事件处理程序的操作:

      addEventListener()添加事件监听程序

      removeEventListener()移除事件监听程序

      三个参数设置;事件名称,处理方法(函数),布尔值--false:表示在冒泡阶段调用事件处理程序(可以最大限度的兼容浏览器),一般设为false;true表示在捕获阶段调用事件处理程序

    来看下面的例子:

    var btn3 = document.getElementById("btn3");
    btn3.addEventListener('click',function(){
        console.log("DOM2级事件处理程序-click-01");
    },false)
    //DOM2级事件处理程序-click-01

    与DOM0级不同的是,我们移除需要使用removeEventListener(),代码如下:

    复制代码
    var btn3 = document.getElementById("btn3");
    btn3.addEventListener('click',function(){
        console.log("DOM2级事件处理程序-click-01");
    },false)
    btn3.removeEventListener('click',function(){
        console.log("DOM2级事件处理程序-click-移除成功");
    },false)
    //DOM2级事件处理程序-click-01
    复制代码

    在这里,你很有可能和我想的一样,DOM2级事件处理程序已经移除完了。但是结果是DOM2级事件处理程序-click-01依然还在,按照理论上来说不应该是被移除了么?但是为什么还在?

    原因:通过addEventListener方法添加的事件处理程序只能用removeEventListener来移除,移除时传入的参数与添加处理程序时使用的参数相同。这就意味着通过addEventListener添加的匿名函数将无法移除。真的是这样么?我们继续来看例子:

    复制代码
    var btn3 = document.getElementById("btn3");
    var handler = function demo3(){
        alert("DOM2级事件处理程序");
    }
    btn3.addEventListener('click',handler,false)
    //在不加后面的代码的时候弹框显示DOM2级事件处理程序
    btn3.removeEventListener('click',handler,false)
    //加了removeEventListener后点击,没有弹框,说明该点击事件已经被移除了
    复制代码

    接下来我们同样思考几个问题:
    思考一:DOM2级事件是否可以同时添加多个不同事件处理方法

    复制代码
    var btn3 = document.getElementById("btn3");
    btn3.addEventListener('click',function(){
        console.log("DOM2级事件处理程序-click-01");
    },false)
    btn3.addEventListener('mouseover',function(){
        console.log("DOM2级事件处理程序-mouseover");
    },false)
    复制代码

    结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01。
    说明:DOM2级事件可以同时添加多个不同事件处理方法

    思考二:DOM2级事件是否可以同时添加多个相同事件处理方法

    复制代码
    var btn3 = document.getElementById("btn3");
    btn3.addEventListener('click',function(){
        console.log("DOM2级事件处理程序-click-01");
    },false)
    btn3.addEventListener('mouseover',function(){
        console.log("DOM2级事件处理程序-mouseover");
    },false)
    btn3.addEventListener('click',function(){
        console.log(this.id);
    },false)
    复制代码

    结果:鼠标经过,控制台出现:DOM2级事件处理程序-mouseover;鼠标点击,控制台出现:DOM2级事件处理程序-click-01和btn3。
    说明:DOM2级事件可以同时添加多个不同事件处理方法,同时在这里可以用this引用被触发的元素。

    注意:
      1、要处理的事件名(事件名去掉“on”,如点击事件需写成click);
      2、作为事件处理程序的函数名(函数不需要带括号,只要函数名或者直接写函数体也可以);
      3、布尔值(true表示使用事件捕获流,false表示使用事件冒泡流,一般使用false兼容各种浏览器)。
      4、通过addEventListener添加的事件只能通过removeEventListener来移除;DOM 0级和DOM 2级事件可以在一个DOM上添加多个事件,按顺序执行
      5、DOM2级事件处理程序适用于:IE9、Firefox、Safari、Chrome和Opera

    思考:那么如果我们想要兼容低版本的IE该怎么办呢?请继续往下阅读

    IE事件处理程序

    (声明:IE事件处理程序的代码没有进行测试)

    IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),这两个方法接受相同的两个参数:事件处理程序名称与事件处理函数程序函数(因为IE8及更早版本只支持时间冒泡,所以没有第三个参数)。下面我们来看代码:

    var btn4 = document.getElementById("btn4");
    btn4.attachEvent('onclick',function(){
        console.log("IE事件处理程序-onclick-01");
    });

    这里需要注意的是,第一个参数是onclick,而不是click。另外与DOM0级中的不同在于作用域,看下面的代码:

    var btn4 = document.getElementById("btn4");
    btn4.attachEvent('onclick',function(){
        console.log(this === window);  //true
    });

    其他需要注意的有:
    1、attachEvent方法也可以为一个元素添加一个或多个事件处理程序,当添加多个相同的事件处理程序的时候,这些事件处理程序不是以添加它们的顺序而执行,而是以相反的顺序被触发。比如下面这个例子:

    复制代码
    var btn4 = document.getElementById("btn4");
    btn4.attachEvent('onclick',function(){
        console.log("hello");
    });
    btn4.attachEvent('onclick',function(){
        console.log("World");
    });
    复制代码

    这段代码用DOM0级的方法得到的结果为World,用DOM2级的方法得到结果为先是hello然后是World,而在这里的结果为先是World然后是hello。

    2、detachEvent()方法和DOM2级中移除的方法一样,同样也需要注意中间添加的匿名函数不能被移除,这里就不再举例说明了。

    从HTML事件处理程序到DOM0级事件处理程序,再到DOM2级处理程序,还有IE事件处理程序,我们在使用的时候为了让用户体验最佳,我们该让我们的代码兼容各种浏览器,那么我们有什么好的方法让我们的代码兼容到各种浏览器中呢?

    跨浏览器的事件处理程序

    为了解决跨浏览器的问题,我们不得不集中的把前面几种方法柔和在一起,然后就出现了跨浏览器的事件处理程序。(我的思路如下)

        1、先考虑添加事件的情况,在添加的时候,我们要知道给谁添加?添加的是什么事件?然后事件触发的是什么函数?
            给谁添加?当然是我们的元素了,因为还不知道,这里我们先设置为element
            添加的是什么事件?click?onclick?onmouseover?我们也还是不知道,先设置为type吧
            添加什么函数?前面的例子中有用到过handler这里也先就用这个

    function(element, type, handler) {……}

        2、我们要判断我们的浏览器是否支持相关的操作,这里我们可以用if-else来判断

    复制代码
    if(element.addEventListener) {
        //DOM2级事件处理程序
    } else if(element.attachEvent) {
        //IE事件处理程序
    } else {
        //DOM0级事件处理程序
    }
    复制代码

        3、针对DOM2级事件处理程序、IE事件处理程序和DOM0级事件处理程序它们有相似的地方,然后我们通过前面可以知道,它们对于事件的处理有点不和谐,我们该怎样呢?很明显,添加"on"比去掉"on"更简单,所以比如点击事件的type应该为click,然后在需要用onclick的地方,使用'on'+type,然后各自保留各自的特点    
    DOM2级事件处理程序:element.addEventListener(type, handler, false);
    IE事件处理程序:element.attachEvent('on' + type, handler);
    DOM0级事件处理程序:element['on' + type] = handler;

        4、到这里基本的添加情况以完成,接下来用同样的方法来考虑移除事件的情况(步骤省略)

        5、然后分别给添加事件函数和移除事件函数进行命名addHandler和deleteHandler,并封装起来,得到我们的最终效果:

    复制代码
    var eventUtil = {
        addHandler: function(element, type, handler) {
            if(element.addEventListener) {
                element.addEventListener(type, handler, false);
            } else if(element.attachEvent) {
                element.attachEvent('on' + type, handler);
            } else {
                element['on' + type] = handler;
            }
        },
        deleteHandler: function(element, type, handler) {
            if(element.removeEventListener) {
                element.removeEventListener(type, handler, false);
            } else if(element.detachEvent) {
                element.detachEvent('on' + type, handler);
            } else {
                element['on' + type] = null;
            }
        }
    }
    复制代码

    事件对象

    DOM中的事件对象

      前面我们已经封装好一个函数了,我们后面的操作直接引用前面的那个函数。说到事件对象,我们先不得不说事件对象的两个重要属性type和target
      其中type是获取事件的类型,而target是获取事件的目标。直接看代码:

    var btn = document.getElementById("btn");
    eventUtil.addHandler(btn,'click',showMes);
    function showMes(event){
        alert(event.type);//结果是弹出click
    }

    结果是弹出click,说明了该事件的类型是点击事件

    var btn = document.getElementById("btn");
    eventUtil.addHandler(btn,'click',showMes);
    function showMes(event){
        alert(event.target);//结果是弹出[object HTMLInputElement]
    }

    结果是弹出[object HTMLInputElement],说明该事件的目标是input元素

    下面我们运行一段代码,来看一个有趣的现象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <div id="box">
        <input type="button" id="btn" value="DOM中的事件对象" />
        <a href="www://baidu.com" id="go">百度</a>
    </div>
     
    function showMes(event){
        alert(event.target.nodeName);
    }
     
    function showBox(){
        alert("这是放盒子的box")
    }
     
    var btn = document.getElementById("btn");
    var box = document.getElementById("box");
     
    eventUtil.addHandler(btn,'click',showMes);
    eventUtil.addHandler(box,'click',showBox);

     运行结果:鼠标点击按钮,分别弹出了“INPUT”和“这是放盒子的box”,我们只点击了按钮,为什么按钮所在的div也会显示呢?查资料知道这是因为事件冒泡机制导致的。

    那么我们该如何阻止事件冒泡呢?

    我们的事件对象给我们提供了一种方法:通过stopPropagation()方法来实现,代码示例如下:

    复制代码
    function showMes(event){
        alert(event.target.nodeName);
        event.stopPropagation();//阻止事件冒泡
    }
    
    function showBox(){
        alert("这是放盒子的box")
    }
    
    var btn = document.getElementById("btn");
    var box = document.getElementById("box");
    
    eventUtil.addHandler(btn,'click',showMes);
    eventUtil.addHandler(box,'click',showBox);
    复制代码

    光知道阻止事件冒泡行为还不行我们还需要知道的一种方法:阻止事件默认行为-->preventDefault()方法

    比如我们知道的a标签有一个默认行为就是跳转到目标地址,如果我们想要阻止这种行为我们就可以使用该方法,代码如下:

    复制代码
    <a href="www://baidu.com" id="go">百度</a>
    
    var go = document.getElementById("go");
    function showGo(event){
        event.stopPropagation();//阻止事件冒泡
        event.preventDefault();//阻止事件的默认行为
    }
    eventUtil.addHandler(go,'click',showGo);
    复制代码

    运行结果是,无论怎样点击,都跳转不到百度页面。
    在a标签里面阻止事件默认行为还有一种方法就是直接在a标签里面添加,比如<a href="javascript:;">aa</a>

    补充:在需要通过一个函数处理多个事件时,可以使用type属性,比如:

    复制代码
    var btn = document.getElementById("btn");
    var handler = function(event){
        switch(event.type){
            case"click":
                alert("Clicked");
                break;
            
            case"mouseover":
                event.target.style.backgroundColor = "red";
                break;
            
            case"mouseout":
                event.target.style.backgroundColor = "";
                break;
        }
    };
    btn.onclick = handler;
    btn.onmouseover = handler;
    btn.onmouseout = handler;
    复制代码

    IE中的事件对象

      【type属性】用于获取事件类型
      【srcElement属性】用于获取事件的目标(DOM中用的是target),所以,可以使用

    event=event || window.event;(考虑IE8以前的版本)
    var ele=event.target  event.srcElement;(考虑非IE与IE)

      【cancelBubble属性】用于阻止事件冒泡,设为true表示阻止冒泡,false表示不阻止冒泡。
      【returnValue属性】用于阻止事件的默认行为,设为false表示阻止。
      这里只介绍IE事件对象的常用属性,关于它的具体例子就不做介绍了

    跨浏览器的事件对象:

    搞清楚前面的问题,下面的一段代码看起来就简单了。它直接将前面提到的所有方法和属性一起封装起来,并保存到一个单独的js文件中,命名为event.js,其代码如下:

    复制代码
    var eventUtil = {
        // 添加句柄
        addHandler: function(element, type, handler) {
            if(element.addEventListener) {
                element.addEventListener(type, handler, false);
            } else if(element.attachEvent) {
                element.attachEvent('on' + type, handler);
            } else {
                element['on' + type] = handler;
            }
        },
        // 删除句柄
        removeHandler: function(element, type, handler) {
            if(element.removeEventListener) {
                element.removeEventListener(type, handler, false);
            } else if(element.detachEvent) {
                element.detachEvent('on' + type, handler);
            } else {
                element['on' + type] = null;
            }
        },
        //获取事件
        getEvent: function(event) {
            return event ? event : window.event;
        },
        //获取事件类型
        getType: function(event) {
            return event.type;
        },
        //获取元素
        getElement: function(event) {
            return event.target || event.srcElement;
        },
        //阻止事件默认行为
        preventDefault: function(event) {
            if(event.preventDefault) {
                event.preventDefault();
            } else {
                event.returnValue = false;
            }
        },
        //阻止事件冒泡
        stopPropagation: function(event) {
            if(event.stopPropagation) {
                event.stopPropagation();
            } else {
                event.cancelBubble = true;
            }
        }
    }
    复制代码

     我们可以用如下代码来测试点击a事件

    复制代码
    window.onload=function(){
      var go=document.getElementById('go'),
          box=document.getElementById('box');
    
      eventUtil.addHandler(box,'click',function(){
          alert('我是整个父盒子');
      });
    
      eventUtil.addHandler(go,'click',function(e){
          //e=eventUtil.getEvent(e);
          e=e || window.event;
          alert(eventUtil.getElement(e).nodeName);
          eventUtil.preventDefault(e);
          eventUtil.stopPropagation(e);
      });
    
    }
    复制代码

    事件类型

    因为事件类型比较多,这里主要通过两个具体的案例来展示,不做详细介绍

    案例一:用鼠标或键盘事件来控制抽奖。

    效果截图:

    查看地址:http://sandbox.runjs.cn/show/0pibyaes

    案例二:模仿扣扣登陆时的拖曳效果(主要学习其中如何实现拖曳效果的思维)

    效果截图:

    查看地址:http://sandbox.runjs.cn/show/7mhb7pfw

    更多参考资料:

      参考书籍:《Javascript高级程序设计(第三版)》

      参考博客: https://my.oschina.net/u/1403185/blog/184960

        http://www.cnblogs.com/luozhihao/p/5934935.html

        http://www.cnblogs.com/xiaohuochai/p/5867195.html

  • 相关阅读:
    ajax怎么打开新窗口具体如何实现
    关于springcloud hystrix 执行 hystrix.stream 跳转失败的问题
    Zookeeper 和Eureka比较
    Maven Install报错:Perhaps you are running on a JRE rather than a JDK?
    Oracle11g卸载步骤
    Oracle数据库备份及恢复
    python是如何进行内存管理的?
    python面试题
    json模块与hashlib模块的使用
    随机验证码、打印进度条、文件copy脚本
  • 原文地址:https://www.cnblogs.com/libin-1/p/5962125.html
Copyright © 2011-2022 走看看