文档章节

浏览器中关于事件的那点事儿

顽Shi
 顽Shi
发布于 2014/01/13 17:24
字数 3114
阅读 5788
收藏 252

    在前端中,有一个很重要的概念就是事件.我对于事件的理解就是使用者对浏览器进行的一个动作,或者说一个操作.

    本文会介绍很多与事件有关的东西,虽然我的出发点有那么点一网打尽的意思,不过也难以盖全.所以就把最常用,最基本也相对重要的内容拿出来记录一下.

Javascript绑定事件的方式

    传统的事件绑定

    因为各种历史原因,事件的绑定在不同的浏览器总是有不同的写法,当然现在可能大多数人都已经习惯于jQuery的事件绑定,而不清楚javascript的原生事件绑定是什么样子.

    非常传统的事件的绑定方式,是在一个元素上直接绑定方法,element.onclick = function(e){}

<body>
    <input type="button" id="bt" name="bt button" value="this is a button">
    <script>
      var bt = document.getElementById("bt");
      bt.onclick = function(e){
        alert("this is a alert");
        alert(e.currentTarget.name);
      }
    </script>
  </body>

    这是传统的事件绑定,它非常简单而且稳定,适应不同浏览器.e表示事件,this指向当前元素.但是这样的绑定只会在事件冒泡中运行,捕获不行.一个元素一次只能绑定一个事件函数.

    W3C方式的事件绑定

    W3C中推荐使用的事件绑定是用addEventListener()函数,element.addEventListener('click',function(e){...},false),上代码:

<body>
    <input type="button" id="bt" name="bt button" value="this is a button">
    <script>
      var bt = document.getElementById("bt");
      bt.addEventListener('click',function(e){
        alert("this is a alert");
        alert(e.currentTarget.name);
      },false);
    </script>
  </body>

    如此的效果和之前的传统绑定方式是一样的,这种绑定同时支持捕获和冒泡,addEventListener()函数最后的函数表达了事件处理的阶段,false(冒泡),true(捕获).这种方式最重要的好处就是对同一元素的同一个类型事件做绑定不会覆盖,会全部生效.比如对上面代码bt元素在进行一次click的绑定,那么两次绑定的事件处理函数都会执行,按照代码书写顺序.

    但是IE浏览器不支持addEventListener()函数,只在IE9以上(包括IE9)可以使用.IE浏览器相应的要使用attachEvent()函数代替.

    IE下的事件绑定

    IE下事件绑定的函数是attachEvent,它支持全系列的IE.但是如果你在Chrome等其他内核浏览器中使用这个函数去绑定事件,浏览器会报错的.

<body>
    <input type="button" id="bt" name="bt button" value="this is a button">
    <script>
      var name = "world";
      var bt = document.getElementById("bt");
      bt.attachEvent('onclick',function(){
        alert("hello "+ this.name);
      });
    </script>
  </body>

    attachEvent()函数支持事件捕获的冒泡阶段,同时它不会覆盖事件的绑定.但是一个缺点就是它处理函数中的this指向的是全局的window,所以上面代码弹出的结果会是"hello world".

冒泡和捕获

    上面的绑定事件中提到了冒泡和捕获阶段的概念,这两个概念对于理解事件是很重要的,对于它们的理解还要涉及到DOM(文档对象模型)和事件流的概念.事件流就是一个事件对象沿着特定数据结构传播的这么一个过程.

    所谓的事件对象就是Event,当一个元素上绑定的事件被触发时会产生一个事件对象,从一切皆对象的观点看这是很符合逻辑的.冒泡和捕获讲的就是事件流在DOM中两种不同的传播方式.对于冒泡和捕获的理解,我们还是从一个小的示例来看:

<body>
    <div id="bt1" style="width:300px;height:300px;border:1px solid red" name="divbt1">
      <div id="bt2" style="width:100px;height:100px;border:1px solid red" name="divbt2"></div>
    </div>
    <script>
      var bt1 = document.getElementById("bt1");
      bt1.onclick = function(e){
        alert("bt1");
      }
      var bt2 = document.getElementById("bt2");
      bt2.onclick = function(e){
        alert("bt2");
      }
    </script>
  </body>

    这里我们使用最简单的,最原始的事件绑定方式.2个div嵌套并且绑定有弹窗事件,那么当我们点击里面的div的时候,两个div的点击事件都会被触发这个是没有疑问的,那么它们的处理函数谁先被执行?

    这里用IE8,9,10和Chrome浏览器同时实验,结构都是先弹出bt2,然后弹出bt1.也就是里面小div的事件先被处理了.我们来思考一下这是什么样的一个顺序,从DOM的结构上看,应该是这样的body > bt1 > bt2.我们把这个结构竖过来,bt2在整个结构的最下面,body在最上面.想象一下,当点击发生时产生一个泡泡(也就是事件对象),然后这个泡泡慢慢向上浮,首先路过bt2,然后路过bt1,在路过它们时依次执行事件函数,这就是冒泡型事件.

    与之相反的就是捕获型事件,它事件流传播的顺序正好与冒泡型事件完全相反.也就是bt1上的事件先触发,然后传递到bt2.捕获是由表及里,冒泡是由内之外.

    那么现在回忆一下之前的W3C标准中那个addEventListener()函数,它里面最后一个参数false代表冒泡,true代表捕获,这是什么意思呢?因为W3C作为一个标准,它选择了一个相对折中的方案去处理事件,也就是任何在W3C事件模型中发生的事件都先进入捕获阶段,然后在进入冒泡阶段,保证一个事件会经历这两个阶段,以适应IE和其他非IE浏览器,这样使用者可以根据需求选择将事件注册到哪一个阶段.

    现在再来看用addEventListener()函数进行事件绑定的结果:

<body>
    <div id="bt1" style="width:300px;height:300px;border:1px solid red" name="divbt1">
      <div id="bt2" style="width:100px;height:100px;border:1px solid red" name="divbt2"></div>
    </div>
    <script>
      var bt1 = document.getElementById("bt1");
      bt1.addEventListener('click',function(e){
        alert("bt1");
      },false);
      var bt2 = document.getElementById("bt2");
      bt2.addEventListener('click',function(e){
        alert("bt2");
      },false);      
    </script>
  </body>

    这里2个div的事件绑定类型一共有4个可能的组合,2个false;2个true;1个false,1个true;1个true,1个false.这里分别试验下吧,记住按照W3C标准,捕获阶段会在冒泡之前.


jQuery绑定事件的方式

    上面我们记录了关于javascript原生的事件绑定的一些写法,这里我们在介绍一下通过jQuery进行事件绑定的方式.首先来夸一夸jQuery的好,通过jQuery绑定让我们省去了考虑浏览器兼容和事件流程序的相关细节内容.jQuery中对于事件的绑定称为委托,这是一个很好的定义,所谓委托,顾名思义就是自己不去做,我让别人帮我做这个事.jQuery就是这么做的,让我们详细了解下.

    .bind()

    我们直接看代码,bind()函数使用很简单.

<body>
  <div id="div1" style="width:300px;height:300px;border:1px solid red" name="divbt1">
  <script>
    $("#div1").bind('click',function(e){
      alert("div1 " + e.currentTarget.name);
    });  
  </script> 
</body>

    代码在IE8,IE11,Chrome运行都没有问题,我们简单翻译一下就是首先找到id为div1的div对象,然后给这个对象绑定一个click事件.现在来分析一下bind(),首先如果用它绑定事件要有一个寻找jQuery对象的过程,其次如果要为大量的元素绑定事件那么要寻找大量的对象不说,每一个对象还要占用内存来存储相应的处理函数.并且bind()只能为当前已存在的DOM节点绑定事件,如果节点还没有产生bind是没有办法的.

    所以说bind()推荐在使用比较简单的情况中,绑定不多的节点并且没有新节点产生的情况.如果比较复杂就推荐使用delegate().

    .delegate()

    在jQuery中还有一个live()函数也能处理类似的问题,但是不如delegate()好用,所以这里就不介绍了.delegate()是为了突破单一bind()方法的局限性,实现事件的委托.我们先看代码来理解:

<body>
  <div id="div1" style="width:300px;height:300px;border:1px solid red" name="divbt1">
    <div id="div2" style="width:100px;height:100px;border:1px solid red" name="divbt2"></div>
  </div>
  <script>
    $("body").delegate('#div1','click',function(e){
      alert("div1");
    });
    $("body").delegate('#div2','click',function(e){
      alert("div2");
    });    
  </script>
</body>

    解读一下delegate()函数,我们寻找到body标签的对象并调用delegate(),这是把事件的执行委托给body.也就是监听整个DOM树,当触发事件的DOM节点的是id为div1,触发触发事件的类型是click时,在事件传播到body时,我们执行相应的处理函数.body怎么能知道这么多,它如何知道绑定在它身上的执行函数什么时候执行?jQuery这些事件委托的原理根据事件冒泡的机制,广播的时候所有的节点都会知道,到底发生了什么!

    DOM在为页面中的每个元素分派事件时,相应的元素一般都在事件冒泡阶段处理事件.在类似 body > div > a 这样的结构中,如果单击a元素,click事件会从a一直冒泡到div和body(即document对象).因此,发生在a上面的单击事件,div和body元素同样可以处理.而利用事件传播(这里是冒泡)这个机制,就可以实现事件委托.具体来说,事件委托就是事件目标自身不处理事件,而是把处理任务委托给其父元素或者祖先元素,甚至根元素(document).

事件的取消

    在一些情况下我们需要阻止事件流的传播,或者解除之前绑定的事件.在实际工作中经常会遇到类似的需求,尤其是事件流的阻止.

    事件流阻止

    某些事件的对象是可以取消的,这意味着可以阻止默认动作的发生.事件对象是否可以取消,要通过Event.cancelable属性表示.事件监听器可以调用Event.preventDefault()取消事件对象的默认动作.Event.stopPropagation()方法可以阻止事件向上冒泡.

    事件的阻止根据场景不同和浏览器不同有不同的处理,因为事件处理模型不同的关系,如果在IE下Event.returnValue = false就可以.如果是非IE下,用Event.preventDefault()阻止.事件流阻止,这里面阻止的是它的继续传播以及有可能产生的默认动作.这里举一个常见且简单的例子,就是submit类型按钮的点击.

<body>
  <form action="asd.action">
    <input type="submit" id="tijiao" value="submit"/>
  </form>
  <script>
    $("body").delegate('#tijiao','click',function(e){
      e.preventDefault();
    });
  </script>
</body>

    这里点击按钮,form表单默认的提交被阻止了,也就是其默认动作终止了.这里有一个强调的就是滚动事件.滚动也是经常遇到需要处理的事件类型,但是滚动的阻止有点特例,它不支持在委托里进行阻止.

    说到这里我们感觉Event.preventDefault()和Event.stopPropagation()都可以阻止事件,那么它们有什么区别?

    前者是通知浏览器不要执行与事件相关联的默认动作,比如submit类型的按钮点击会提交.后者是停止事件流的继续冒泡,但是它对IE8及以下IE浏览器支持不好.如果直接使用return false则表示终止处理函数.

    事件函数的解除绑定

    和事件的绑定其实是相对应的,如果需要接触事件的绑定,运行对应的函数就可以了.如果是原生JS绑定则对应运行removeEventListener()和detachEvent().看一个代码示例:

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;
    }
  }             
 };

    如果是jQuery的绑定,也是存在对应的解绑函数用以清除注册事件,比如unbind()和undelegate().


© 著作权归作者所有

共有 人打赏支持
顽Shi
粉丝 272
博文 81
码字总数 92387
作品 0
普陀
程序员
加载中

评论(22)

daydayhappy
daydayhappy
戴菲菲
wsqzz
wsqzz
一直不理解jquery同样功能有多个方法的原因,看来没理解到位
查理乐
查理乐
Ii
归海一刀
归海一刀
好文!!
顽Shi
顽Shi

引用来自“那点事儿”的评论

之前用一直都是直接使用 .click .change等 很少用到bind 也没听过delegate、live等 而且以前都不知道他们的区别 看来这次涨知识了 2

13~有用就好
netkiller-
netkiller-
犀利糊涂用了很多年
别人的名字
别人的名字
之前用一直都是直接使用 .click .change等 很少用到bind 也没听过delegate、live等 而且以前都不知道他们的区别 看来这次涨知识了 2
顽Shi
顽Shi

引用来自“mark35”的评论

引用来自“gjl87910lq”的评论

顶一个!
最新的jQuery推荐使用on来代替bind了。
另外,事件中event的target和currentTarget也值得注意一下。

新版jquery用on()来统一代替live/bind/delegate,包括特定的事件(比如click())也可以用on来操作

这确实是推荐使用的~4
mark35
mark35

引用来自“gjl87910lq”的评论

顶一个!
最新的jQuery推荐使用on来代替bind了。
另外,事件中event的target和currentTarget也值得注意一下。

新版jquery用on()来统一代替live/bind/delegate,包括特定的事件(比如click())也可以用on来操作
Glitter
Glitter
说的很不错~
关于dom ready的那点事儿

终于学生生涯最后一个寒假也就这么的过完了,昨天回到川大,休息了一天,今天正式开始做毕设了。 晚上看完 年代秀,不想码代码,所以就把寒假在家学习的一些分享出来。 之前在学校,由于项目...

阳光test
2013/02/25
0
1
Netty的那点事儿

Netty是一个基于异步与事件驱动的网络应用程序框架,它支持快速与简单地开发可维护的高性能的服务器与客户端。 所谓事件驱动就是由通过各种事件响应来决定程序的流程,在Netty中到处都充满了...

SylvanasSun
2017/12/02
0
0
我的友情链接

新浪硬件 3GP手机视频下载 btchina seven 陈皓的个人专栏 《Java程序员,上班那点事儿》的那点事儿 李天平 Java究竟怎么玩 豆子空间 子 孑 xql888 ITMOV旗舰 Simon Xiao 肖舸的blog 我的数据...

leizhimin
2017/11/22
0
0
【Maven 那点事儿】中的图是拿什么画的呀

@黄勇 你好,想跟你请教个问题: 【Maven 那点事儿】中的图http://my.oschina.net/huangyong/blog/194583是拿什么画的呀?

bopjiang
2014/12/12
80
0
朴素贝叶斯的那点事儿

在机器学习领域中,朴素贝叶斯是一种基于贝叶斯定理的简单概率分类器(分类又被称为监督式学习,所谓监督式学习即从已知样本数据中的特征信息去推测可能出现的输出以完成分类,反之聚类问题被...

SylvanasSun
2017/12/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Ubuntu18.04 显卡GF-940MX安装NVIDIA-390.77

解决办法: 下面就给大家一个正确的姿势在Ubuntu上安装Nvidia驱动: (a)首先去N卡官网下载自己显卡对应的驱动:www.geforce.cn/drivers (b)下载后好放在英文路径的目录下,怎么简单怎么来...

AI_SKI
今天
0
0
深夜胡思乱想

魔兽世界 最近魔兽世界出了新版本, 周末两天升到了满级,比之前的版本体验好很多,做任务不用抢怪了,不用组队打怪也是共享拾取的。技能简化了很多,哪个亮按哪个。 运维 服务器 产品 之间的...

Firxiao
今天
1
0
MySQL 8 在 Windows 下安装及使用

MySQL 8 带来了全新的体验,比如支持 NoSQL、JSON 等,拥有比 MySQL 5.7 两倍以上的性能提升。本文讲解如何在 Windows 下安装 MySQL 8,以及基本的 MySQL 用法。 下载 下载地址 https://dev....

waylau
今天
0
0
微信第三方平台 access_token is invalid or not latest

微信第三方开发平台code换session_key说的特别容易,但是我一使用就带来无穷无尽的烦恼,搞了一整天也无济于事. 现在记录一下解决问题的过程,方便后来人参考. 我遇到的这个问题搜索了整个网络也...

自由的开源
今天
3
0
openJDK之sun.misc.Unsafe类CAS底层实现

注:这篇文章参考了https://www.cnblogs.com/snowater/p/8303698.html 1.sun.misc.Unsafe中CAS方法 在sun.misc.Unsafe中CAS方法如下: compareAndSwapObject(java.lang.Object arg0, long a......

汉斯-冯-拉特
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部