关于前端事件的那些事儿

事件这个东西呢?是一个很神奇的东西,搞的我很烦 无论是在Android端还是前端。今天巧借这点时间呢?就准备来写一写这个东西。先写一篇前端的事件,然后再转向Android端写一篇Android端的事件 perfect...

一、 什么叫事件流

事件流的描述呢?很简单:就是描述从页面接收到事件的顺序。现今主流的两大阵营对此提出了两种相反的事件流:ie提出的称之为事件冒泡;Netscape communicator提出的则是事件捕获。那么什么是事件冒泡和事件捕获呢?

  • 事件冒泡

IE的事件流叫做事件冒泡,说白了冒泡就是从嵌套最深的那个节点接收,然后逐级向上冒泡直到document的过程。用下面的html为例子:




    我是测试


    
click Me

如果你单击了div元素,那么这个click事件就这么传递:
1、 div
2、 body
3、 html
4、document
也就是说,当我们点击了div元素之后,click事件首先在div元素上面发生,这个元素就是我们单击的元素。然后click事件就顺着DOM树向上传播,在每一级上面都会发生,一直传到document。


  • 事件捕获

Netscape communicator团队提出了另一种事件流叫做事件捕获。事件捕获的过程呢?则是从document开始一直传递到div的过程:
1、document
2、 html
3、 body
4、 div
示意图如下:


  • DOM的事件流

通常的事件流包括三个步骤:事件捕获阶段、处于目标阶段和事件冒泡阶段。用前面的html表示,大概就是这样。



事件在处于捕获阶段的时候呢?是不会接收到事件的,然后再目标阶段的时候,事件发生在div元素中,然后通过冒泡传回文档流

二、 事件的处理程序

事件呢?就是:用户或者浏览器自身执行的某种动作。比如click,load和mouseover。而相应某个事件的函数叫做事件处理程序。一般都是on开头的,onclick就是click的处理程序、onload就是load的处理程序等等...
首先 我们直接用代码来写主要的几种类型:

1、 html事件处理程序:

    

    function showMessage(){
      alert("hello my friends!")
    }

2、 Dom0事件处理程序:

        

        var btn2 = document.getElementById("myBtn2");
        btn.onclick = function(){
          alert("hello my friends!");
        }
        btn.onclick = null;  //移除事件

3、 Dom2事件处理程序:

这里和前面不同,dom2定义了两个方法:addEventListener()和removeEventListener()分别用来绑定和删除事件。所有的dom节点都包含这个方法,并且它接受三个参数:处理的事件名、作为事件处理的函数和一个布尔值。如果这个布尔值是true,表示在捕获阶段调用事件程序;如果是false,则表示在冒泡阶段调用事件处理程序。

事件绑定如下:

    

    var btn = document.getElementById("myBtn");
    btn.addEventListener("click",function(){
        alert("hello klivitam!")
    },false)

关于事件绑定和移除有这么几个要注意的点:

  • 事件多次绑定
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
    alert("hello klivitam!")
},false)

btn.addEventListener("click",function(){
    alert("123456")
},false)

如果重复进行添加事件的话,这样会先出按照顺序 先显示hello klivitam,然后再显示123456

  • 事件移除
    先看下面一段代码
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click",function(){
            alert("hello klivitam!")
        },false)

        btn.removeEventListener("click",function(){
            alert("hello klivitam!")
        },false)

我们知道:利用addEventListener()绑定的事件,需要用removeEventListener()来将事件的监听移除,并且removeEventListener()的参数必须和addEventListener()的参数相同。这里我们虽然看似两者是相同的,但是并不能移除。这是值得注意的,针对这种情况,我们应该这么写:

        var handle = function(){
            alert("hello klivitam!")
        }
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click",handle,false)

        btn.removeEventListener("click",handle,false)

3、 IE事件处理程序:

ie中存在和dom中想同的两个方法:attachEvent和detachEvent。这两个方法接受想同的两个参数:事件处理程序的名称与事件处理程序函数。
首先我们先写一个绑定的实例:

        var btn2 = document.getElementById("myBtn2");

        btn2.attachEvent("onclick",function(){
            alert(window === this);
        })

        btn2.attachEvent("onclick",function(){
            alert("boy");
        })

同样和dom2的一样的操作,这里只不过函数名变了。这里值得注意的是:window和this相等。当点击btn2元素的时候,首先会提示true,然后再提示boy。并且在移除代码的时候,和dom2的操作一样

        var btn2 = document.getElementById("myBtn2");

        var handle = function(){
            alert("hello klivitam!")
        }

        btn2.attachEvent("onclick",handle)

        btn2.detachEvent("onclick",handle)

4、 跨浏览器的事件处理程序:

前面说了这么多方面的事件处理程序,那么我们在进行开发的时候很可能会需要适配很多浏览器。我们在每次事件绑定的时候,肯定要考虑到诸多版本,诸多内核的浏览器。为了解决这种逻辑,我们就可以进行封装处理。我在这里是这样进行处理的:

        var EventUtils = {
            addHandler:function(ele,type,hander){
                if(ele.addEventListener){
                    ele.addEventListener(type,hander,false);
                }else if(ele.attachEvent){
                    ele.attachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = hander;
                }
            }

            removeHandler:function(ele,type,hander){
                if(ele.removeEventListener){
                    ele.removeEventListener(type,hander,false);
                }else if(ele.detachEvent){
                    ele.detachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = null;
                }
            }
        }

首先,我们在绑定事件的时候呢?绑定事件的时候首先判断是否支持dom2的事件处理程序,如果不支持,那么继续判断是否支持ie的事件处理程序,如若还不支持就只能用通用的事件处理程序。解绑的时候也是一样,先判断是否支持dom2事件处理程序,接着判断ie的事件处理程序,最后在都不支持的情况下使用通用的事件处理程序。

我们在使用的时候就可以这样写一下就好了:

        var btn2 = document.getElementById("myBtn2");

        var handle = function(){
            alert("hello klivitam!");
        }

        EventUtils.addHandler(btn2,"click",hander);

        EventUtils.removeHandler(btn,"click",hander);

当然这个封装还室友点瑕疵的,比如不知道this的指向等等,当然 我在后续的文章中会继续的去讨论封装这个组件的。

三、 事件的对象

我们知道在触发dom上的某个事件对象的时候,这个对象会包含着所有与事件有关的信息。包括事件的元素、事件的类型以及其他与特定事件相关的信息。

  • dom的事件对象

兼容dom的浏览器会将一个event对象传到事件处理程序中。例如:

        btn.addEventListener("click",function(e){
            console.log(e);
        },false)

        btn.onclick = function(e){
            console.log(e);
        };

上图是我打印出来传递的event对象中所包含的值。我在这里呢?就简单的介绍几个最基本的含义,剩下的其实大多数大家都能看懂。

属性/方法 类型 读写 说明
bubbles boolean 只读 表明事件是否冒泡
cancelable boolean 只读 表明是否可以取消事件的默认行为
currentTarget Element 只读 其事件的处理程序正在处理程序的那个元素
defaultPrevented boolean 只读 为true表示已经调用了preventDefault(dom3新增的)
detail Integer 只读 与事件相关的详细细节
eventPhase Integer 只读 调用事件处理程序的阶段:1表示捕获,2处于目标,3处于冒泡
preventDefault() Function 只读 取消事件的 默认行为,如果cancelable为true才能使用这个方法
stopImmediatePropagation Function 只读 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(dom3新增)
stopPropagation Function 只读 取消事件的进一步捕获或者冒泡。如果cancelable为true的时候,才能使用这个方法
target Element 只读 事件的目标
trusted Boolean 只读 为true表示事件是浏览器生成的。为false表示事件又开发者通过js创建的(dom3新增的)
type String 只读 被触发事件的类型
view abstractview 只读 与事件相关联的抽象视图,等同于发生事件的window对象

上面表格中值得注意三点的就是:

  • this是始终等于currentTarget的值,而target则只包含事件的实际目标

  • 当cancalable为true的时候,我们可以通过preventDefault函数来取消其默认行为

  • eventPhase可以用来判断事件的状态,捕获状态为1,目标对象上为2,冒泡上为3 。值得注意的是,当eventPhase为2的时候this,target,currentTarget是始终相等的。

  • ie的事件对象

和访问dom中的event不同,ie中访问event对象有几种不同的方式。来看下面的例子

btn.onclick = function(){
    var e = window.event;
    console.log(e);
};
btn.attachEvent("onclick",function(e){
    console.log(e)
})

同样我也将ie事件的主要的几个属性也列在下面

属性/方法 类型 读写 说明
cancelBubble boolean 读/写 默认值为false,但将其设置为true就可以取消事件(和dom中的stopPropagation()方法作用相同)
returnValue boolean 读写 默认值为true,但将其设置为false就可以取消事件默认行为(跟dom中的preventDefault()方法的作用相同)
srcElement element 只读 事件的目标(雷同dom中的target)
type String 只读 被触发的事件的类型

上面也值得我们注意的几点是:

  • 在事件处理程序中(dom中),srcElemet属性等于this,但是attachEvent this指向的是window
btn.onclick = function(){
    console.log(window.event.srcElement === this); // true
};
btn.attachEvent("onclick",function(e){
    console.log(e.srcElement === this) //false
})
  • 由前面表格所说returnValue属性相当于DOM中的preventDefault()方法,能取消事件的默认行为。在ie中,只要将returnValue设置成false,就可以组织默认行为了。

  • 跨浏览器的事件对象

前面仅仅封装了事件的绑定和解绑,但是我们考虑到我们有的时候可能会用到事件的event对象,前面的介绍可以知道ie和dom中会有些许不同,此时我们就可以对事件进行再次封装。代码如下:

    var EventUtils = {
            addHandler:function(ele,type,hander){
                if(ele.addEventListener){
                    ele.addEventListener(type,hander,false);
                }else if(ele.attachEvent){
                    ele.attachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = hander;
                }
            }

            getEvent:function(e){
                return e?e:window.event;
            }

            getTarget:function(e){
                return e.target||e.srcElement;
            }

            preventDefault:function(e){
                if(e.preventDefault){
                    e.preventDefault()
                }else{
                    e.returnValue = false;
                }

            }

            removeHandler:function(ele,type,hander){
                if(ele.removeEventListener){
                    ele.removeEventListener(type,hander,false);
                }else if(ele.detachEvent){
                    ele.detachEvent("on"+type,hander);
                }else{
                    ele["on"+type] = null;
                }
            }

            stopPropagation:function(e){
                if(e.stopPropagation){
                    e.stopPropagation();
                }else{
                    e.cancelBubble = true;
                }
            }
        }

这里呢?主要总结了事件冒泡的一些跨浏览器的兼容性问题,当然值得注意的是IE 不支持事件捕获的,所以这个方法不是不支持事件捕获的。

说在最后

十一呢?去过漂流,也去爬山,然后腿也瘸了。之后在家看了几天的书籍。怎么说呢?领悟到活着的好处,也领悟到活蹦乱跳的快乐了。我已经决心不在做一个秀才,突然有个瞬间觉得这个城市这么大 我却没去过几处的感觉。
嗯!关于接下来几个月的规划呢?我会每月写一篇书评,每周写一篇Android,写一篇前端、写一篇小程序的文章来鞭策自己不断的向前。敬请期待吧

你可能感兴趣的:(关于前端事件的那些事儿)