关于前端事件的那些事儿

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

一、 什么叫事件流

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

  • 事件冒泡

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

<!DOCTYPE html>
<html>
<head>
    <title>我是测试</title>
</head>
<body>
    <div >click Me</div>
</body>
</html>

如果你单击了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事件处理程序:

    <button id="myBtn1" onclick="showMessage()">我是 html事件处理程序</button>

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

2、 Dom0事件处理程序:

        <button id="myBtn2">我是dom0事件处理程序</button>

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

3、 Dom2事件处理程序:

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

事件绑定如下:

    <button id="myBtn">我是dom2事件处理程序</button>

    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,写一篇前端、写一篇小程序的文章来鞭策自己不断的向前。敬请期待吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容