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

标签: 浏览器 事件 | 发表时间:2014-02-01 20:22 | 作者:
出处:http://kb.cnblogs.com/

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

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

  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()。

相关 [浏览器 事件] 推荐:

浏览器事件的思考

- - 博客 - 伯乐在线
doSomeThing();//这个方法貌似很有名. 上面的代码看起来像是很好的完成了我们交给它的工作, 浏览器不会再将我们重定向到href中的链接,但这么做到底有什么不对呢. 在解释有什么不对前,我们来看看浏览器中事件中的几个概念. 当我们点击某一个链接的时候,浏览器会直接跳转,在表单中按回车,表单会自动提交,这些都是浏览器的默认行为.

跨浏览器resize事件分析

- - CSDN博客Web前端推荐文章
window一次resize事件:. IE7 触发3次, IE8 触发2次, IE9 触发1次, IE10 触发1次. window resize时,部分组件需要重置大小(一次);部分组件不需要重置大小;. 优点:分Debounced和Throttled两种类型,类型明确. 大多数场景使用jquery-smartresize的Debounced即可满足一次调用即可.

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

- - 博客园_知识库
  在前端中,有一个很重要的概念就是事件. 我对于事件的理解就是使用者对浏览器进行的一个动作,或者说一个操作.   本文会介绍很多与事件有关的东西,虽然我的出发点有那么点一网打尽的意思m不过也难以盖全,所以就把最常用,最基本也相对重要的内容拿出来记录一下.   Javascript绑定事件的方式.   因为各种历史原因,事件的绑定在不同的浏览器总是有不同的写法,当然现在可能大多数人都已经习惯于jQuery的事件绑定,而不清楚javascript的原生事件绑定是什么样子.

JavaScript单线程和浏览器事件循环简述

- - 破狼 Blog
JavaScript单线程. 在上篇博客 《Promise的前世今生和妙用技巧》的开篇中,我们曾简述了JavaScript的单线程机制和浏览器的事件模型. 应很多网友的回复,在这篇文章中将继续展开这一个话题. 当然这里是博主的一些理解,如果还存在什么纰漏的话,请不吝指教. JavaScript这门语言运行在浏览器中,是以单线程的方式运行的.

兼容所有浏览器的 DOM 载入事件

- - Harttle Land
本文就页面载入问题讨论 DOMContentLoaded、 load、 readyState等DOM事件的浏览器兼容性, 并给出怎样绑定DOM载入事件以兼容所有的浏览器. 接着介绍jQuery对该问题的实现源码,以及jQuery中 $(document).ready()和 $(window).load()方法的区别.

javaScript跨浏览器事件处理程序

- - SegmentFault 最新的文章
最近在阅读 javascript高级程序设计,事件这一块还是有很多东西要学的,就把一些思考和总结记录下. 在事件处理,事件对象,阻止事件的传播等方法或对象存在着浏览器兼容性问题,开发过程中最好编写成一个通用的事件处理工具. //在这里添加一些通用的事件处理方法. 事件的绑定主要为IE8以下浏览器做兼容处理:.

兼容各个浏览器版本的事件监听器工具

- - CSDN博客推荐文章
作者:kingwolf_JavaScript 发表于2012-5-15 19:12:15 原文链接. 阅读:6 评论:0 查看评论.

[译]跨浏览器的多点触控与鼠标事件处理

- Stanley - UED TEAM,用户体验设计,web前端开发
原作者Ted Johnson, Graphics Program Manager Lead, Internet Explorer. 译者sunnylqm,转发请注明. 本文主要解释了Web开发者如何通过使用IE10中新引入的pointer事件模型、iOS上的touch事件模型以及W3C标准的扩展鼠标事件模型来编写普适的跨浏览器的触控事件处理代码.

各种浏览器全屏模式的方法、属性和事件介绍

- - Web前端 - ITeye博客
requestFullscreen仍然需要附带各浏览器的js方言前缀,相信下面这段代码需要你花大量的搜索才能凑齐:. // 判断各种浏览器,找到正确的方法 function launchFullscreen(element) { if(element.requestFullscreen) {. element.msRequestFullscreen(); } } // 启动全屏.

浏览器事件模型中捕获阶段、目标阶段、冒泡阶段实例详解

- - SegmentFault 最新的文章
如果对事件大概了解,可能知道有事件冒泡这回事,但是冒泡、捕获、传播这些机制可能还没有深入的研究实践一下,我抽时间整理了一下相关的知识. 本文主要对事件机制一些细节进行讨论,过于基础的事件绑定知识方法没有介绍. 特别少的篇幅关注浏览器兼容问题,毕竟原理了解了,兼容性问题可以自己想办法解决了. 在浏览器相对标准化之前,各个浏览器厂商都是自己实现的事件模型,有的用了冒泡,有的用了捕获,W3C为了兼顾之前的标准,将事件发生定义成如下三个阶段:.