【转】JavaScript中的事件处理机制

什么是事件?

事件(Event)是JavaScript应用跳动的心脏 ,也是把全部东西粘在一块儿的胶水。当咱们与浏览器中 Web 页面进行某些类型的交互时,事件就发生了。事件多是用户在某些内容上的点击、鼠标通过某个特定元素或按下键盘上的某些按键。事件还多是 Web 浏览器中发生的事情,好比说某个 Web 页面加载完成,或者是用户滚动窗口或改变窗口大小。javascript

经过使用 JavaScript ,你能够监听特定事件的发生,并规定让某些事件发生以对这些事件作出响应。php

今天的事件

在漫长的演变史,咱们已经告别了内嵌式的事件处理方式(直接将事件处理器放在 HTML 元素以内来使用)。今天的事件,它已经是DOM的重要组成部分,遗憾的是, IE继续保留它最先在IE4.0中实现的事件模型,之后的IE版本中也没有作太大的改变,这也就是说IE仍是使用的是一种专有的事件模型(冒泡型),而其它的主流浏览器直到DOM 级别 3 规定定案后,才陆陆续续支持DOM标准的事件处理模型 — 捕获型与冒泡型。css

历史缘由是:W3C 规范 在DOM 级别 1中并无定义任何的事件,直到发布于 2000 年 11 月 的DOM 级别 2 才定义了一小部分子集,DOM 级别 2中已经提供了提供了一种更详细的更细致的方式以控制 Web 页面中的事件,最后,完整的事件是在2004年 DOM 级别 3的规定中才最终定案。由于IE4是1995推出的并已实现了本身的事件模型(冒泡型),当时根本就没有DOM标准,不过在之后的DOM标准规范过程当中已经把IE的事件模型吸取到了其中。html

目前除IE浏览器外,其它主流的Firefox, Opera,
Safari都支持标准的DOM事件处理模型。IE仍然使用本身专有的事件模型,即冒泡型,它事件模型的一部份被DOM标准采用,这点对于开发者来讲也是有好处的,只有使用
DOM标准,IE都共有的事件处理方式才能有效的跨浏览器。java

DOM事件流

DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所通过的节点都会收到该事件,这个传播过程可称为DOM事件流。node

事件顺序有两种类型:事件捕捉事件冒泡浏览器

冒泡型事件(Event Bubbling)dom

这是IE浏览器对事件模型的实现,也是最容易理解的,至少笔者以为比较符合实际的。冒泡,顾名思义,事件像个水中的气泡同样一直往上冒,直到顶端。从
DOM树型结构上理解,就是事件由叶子节点沿祖先结点一直向上传递直到根节点;从浏览器界面视图HTML元素排列层次上理解就是事件由具备从属关系的最肯定的目标元素一直传递到最不肯定的目标元素.冒泡技术.冒泡型事件的基本思想,事件按照从特定的事件目标开始到最不肯定的事件目标.函数

捕获型事件(Event Capturing)性能

Netscape 的实现,它与冒泡型恰好相反,由DOM树最顶层元素一直到最精确的元素,这个事件模型对于开发者来讲(至少是我..)有点费解,由于直观上的理解应该如同冒泡型,事件传递应该由最肯定的元素,即事件产生元素开始。

 

event-buddle

DOM标准的事件模型

咱们已经对上面两个不一样的事件模型进行了解释和对比。DOM标准同时支持两种事件模型,即捕获型事件冒泡型事件,可是,捕获型事件先发生。两种事件流都会触发DOM中的全部对象,从document对象开始,也在document对象结束(大部分兼容标准的浏览器会继续将事件是捕捉/冒泡延续到window对象)。

domevent

如图:首先是捕获式传递事件,接着是冒泡式传递,因此,若是一个处理函数既注册了捕获型事件的监听,又注册冒泡型事件监听,那么在DOM事件模型中它就会被调用两次。

DOM标准的事件模型最独特的性质是,文本节点也会触发事件(在IE不会)。

capturing-bubbling

事件传送

为了更好的说明DOM标准中的事件流原理,咱们把它放在“事件传送”小结里来更具体的解释。

显然,若是为一个超连接添加了click事件监听器,那么当该连接被点击时该事件监听器就会被执行。但若是把该事件监听器指派给了包含该连接的p元素或者位于DOM树顶端的document节点,那么点击该连接也一样会触发该事件监听器。这是由于事件不只仅对触发的目标元素产生影响,它们还会对沿着DOM结构的全部元素产生影响。这就是你们所熟悉的事件转送

W3C事件模型中明确地指出了事件转送的原理。事件传送能够分为3个阶段。

Standard-event-propagation

如图:标准的事件转送模式

(1).在事件捕捉(Capturing)阶段,事件将沿着DOM树向下转送,目标节点的每个祖先节点,直至目标节点。例如,若用户单击了一个超连接,则该单击事件将从document节点转送到html元素,body元素以及包含该连接的p元素。

在此过程当中,浏览器都会检测针对该事件的捕捉事件监听器,而且运行这件事件监听器。

(2). 在目标(target)阶段,浏览器在查找到已经指定给目标事件的事件监听器以后,就会运行 该事件监听器。目标节点就是触发事件的DOM节点。例如,若是用户单击一个超连接,那么该连接就是目标节点(此时的目标节点其实是超连接内的文本节点)。

(3).在冒泡(Bubbling)阶段,事件将沿着DOM树向上转送,再次逐个访问目标元素的祖先节点到document节点。该过程当中的每一步。浏览器都将检测那些不是捕捉事件监听器的事件监听器,并执行它们。

并不是全部的事件都会通过冒泡阶段的

全部的事件都要通过捕捉阶段和目标阶段,可是有些事件会跳过冒泡阶段。例如,让元素得到输入焦点的focus事件以及失去输入焦点的blur事件就都不会冒泡。

 

事件句柄和事件接听器

事件句柄

事件句柄(又称事件处理函数,DOM称之为事件监听函数),用于响应某个事件而调用的函数称为事件处理函数
。每个事件均对应一个事件句柄,在程序执行时,将相应的函数或语句指定给事件句柄,则在该事件发生时,浏览器便执行指定的函数或语句,从而实现网页内容与用户操做的交互。当浏览器检测到某事件发生时,便查找该事件对应的事件句柄有没有被赋值,若是有,则执行该事件句柄。

咱们认为响应点击事件的函数是onclick事件处理函数。之前,事件处理函数有两种分配方式:在JavaScript中或者在HTML中

若是在JavaScript 中分配事件处理函数, 则须要首先得到要处理的对象的一引用,而后将函数赋值给对应的事件处理函数属性,请看一个简单的例子:

 

1 var link=document.getElementById("mylink");
2 link.onclick=function(){
3   alert("I was clicked !");
4 }; 

 

从咱们看到的例子中,咱们发现使用事件句柄很容易,
不过事件处理函数名称必须是小写的,还有就是只有在
元素载入完成以后才能将事件句柄赋给元素,否则会有异常。

关于文档载入技术,请看《window.onload加载的多种解决方案》 的文章。

若是在HTML中分配事件句柄的话,则直接经过HTML属性来设置事件处理函数就好了,并在其中包含合适的脚本做为特性值就能够了,例如:

 <a href="/" onclick="JavaScript code here">......</a> 

这种JavaScript 代码和经过HTML的style属性直接将CSS属性赋给元素相似。这样会代码看起来一团糟,也违背了将实现动态行为的代码与显示文档静态内容的代码相分离的原则。从1998年开始,这种写法就过期了。

这种传统的事件绑定技术,优缺点是显然的:

*简单方便,在HTML中直接书写处理函数的代码块,在JS中给元素对应事件属性赋值便可。

*IE与DOM标准都支持的一种方法,它在IE与DOM标准中都是在事件冒泡过程当中被调用的。

*能够在处理函数块内直接用this引用注册事件的元素,this引用的是当前元素。

*要给元素注册多个监听器,就不能用这方法了。

事件接听器

除了前面已经介绍的简单事件句柄以外,如今大多数浏览器都内置了一些更高级的事件处理方式,即,事件监听器,这种处理方式就不受一个元素只能绑定一个事件句柄的限制。

咱们已经知道了事件句柄与事件监听器的最大不一样之处是使用事件句柄时一次只能插接一个事件句柄,但对于事件监听器,一次能够插接多个。

IE下的事件监听器:

IE提供的倒是一种自有的,彻底不一样的甚至存在BUG的事件监听器,所以若是要让脚本在本浏览器中正常运行的话,就必须使用IE所支持的事件监听器。另外,Safari 浏览器中的事件监听器有时也存在一点不一样。

在IE中,每一个元素和window对象都有两个方法:attachEvent方法和detachEvent方法。 

1 element.attachEvent("onevent",eventListener);

此方法的意思是在IE中要想给一个元素的事件附加事件处理函数,必须调用attachEvent方法才能建立一个事件监听器。attachEvent方法容许外界注册该元素多个事件监听器。

attachEvent接受两个参数。第一个参数是事件类型名,第二个参数eventListener是回调处理函数。这里得说明一下,有个常常会出错的地方,IE下
利用attachEvent注册的处理函数调用时this指向再也不是先前注册事件的元素,这时的this为window对象。还有一点是此方法的事件类型名称必须加上一个”on”的前缀(如onclick)。 

1 element.attachEvent("onevent",eventListener);

要想移除先前元素注册的事件监听器,可使用detachEvent方法进行删除,参数相同。

DOM标准下的事件监听器:

在支持W3C标准事件监听器的浏览器中,对每一个支持事件的对象均可以使用addEventListener方法。该方法既支持注册冒泡型事件处理,又支持捕获型事件处理。因此与IE浏览器中注册元素事件监听器方式有所不一样的。

1 //标准语法 
2 element.addEventListener('event', eventListener, useCapture);
3 //默认
4 element.addEventListener('event', eventListener, false);

addEventListener方法接受三个参数。第一个参数是事件类型名,值得注意的是,这里事件类型名称与IE的不一样,事件类型名是没’on’开头的;第二个参数eventListener是回调处理函数(即监听器函数);第三个参数注明该处理回调函数是在事件传递过程当中的捕获阶段被调用仍是冒泡阶段被调用 ,一般此参数一般会设置为false(为false时是冒泡),那么,若是将其值设置为true,那就建立一个捕捉事件监听器。

移除已注册的事件监听器调用element的removeEventListener方法便可,参数相同。

1 //标准语法 
2 element.removeEventListener('event', eventListener, useCapture);
3 //默认
4 element.removeEventListener('event', eventListener, false);

经过addEventListener方法添加的事件处理函数,必须使用removeEventListener方法才能删除,并且要求参数与添加事件处理函数时addEventListener方法的参数彻底一致(包括useCapture参数),不然将不能成功删除事件处理函数。

跨浏览器的注册与移除元素事件监听器方案

咱们如今已经知道,对于支持addEventListener方法的浏览器,只要须要事件监听器脚本就都须要调用addEventListener方法;而对于不支持该方法的IE浏览器,使用事件监听器时则须要调用attachEvent方法。要确保浏览器使用正确的方法其实并不困难,只须要经过一个if-else语句来检测当前浏览器中是否存在addEventListener方法或attachEvent方法便可。

这样的方式就能够实现一个跨浏览器的注册与移除元素事件监听器方案:

 1 var EventUtil = {
 2   //注册
 3   addHandler: function(element, type, handler){
 4     if (element.addEventListener){
 5       element.addEventListener(type, handler, false);
 6     } else if (element.attachEvent){
 7       element.attachEvent("on" + type, handler);
 8     } else {
 9       element["on" + type] = handler;
10     }
11   },
12   //移除注册
13   removeHandler: function(element, type, handler){
14     if (element.removeEventListener){
15             element.removeEventListener(type, handler, false);
16     } else if (element.detachEvent){
17             element.detachEvent("on" + type, handler);
18     } else {
19             element["on" + type] = null;
20     }
21   }             
22  }; 

 

事件对象引用

为了更好的处理事件,你能够根据所发生的事件的特定属性来采起不一样的操做。

如事件模型同样,IE 和其余浏览器处理方法不一样:IE 使用一个叫作 event 的全局事件对象来处理对象(它能够在全局变量window.event中找到),而其它全部浏览器采用的 W3C 推荐的方式,则使用独立的包含事件对象的参数传递。

跨浏览器实现这样的功能时,最多见的问题就是获取事件自己的引用及获取该事件的目标元素的引用。

下面这段代码就为你解决了这个问题:

1 var EventUtil ={
2   getEvent: function(event){
3     return event ? event : window.event;
4   },
5   getTarget: function(event){
6     return event.target || event.srcElement;
7   }
8 };

 

中止事件冒泡和阻止事件的默认行为

“中止事件冒泡“和”阻止浏览器的默认行为“,这两个概念很是重要,它们对复杂的应用程序处理很是有用。

1.中止事件冒泡

中止事件冒泡是指,中止冒泡型事件的进一步传递(取消事件传递,不仅是中止IE和DOM标准共有的冒泡型事件,咱们还能够中止支持DOM标准浏览器的捕捉型事件,用topPropagation()方法)。例如上图中的冒泡型事件传递中,在body处理中止事件传递后,位于上层的document的事件监听器就再也不收到通知,再也不被处理。

2.阻止事件的默认行为

中止事件的默认行为是指,一般浏览器在事件传递并处理完后会执行与该事件关联的默认动做(若是存在这样的动做)。例如,若是表单中input type 属性是 “submit”,点击后在事件传播完浏览器就自动提交表单。又例如,input 元素的 keydown 事件发生并处理后,浏览器默认会将用户键入的字符自动追加到 input 元素的值中。

中止事件冒泡的处理方法

在IE下,经过设置event对象的cancelBubble为true便可。

1 function someHandle() {
2   window.event.cancelBubble = true;
3 }

DOM标准经过调用event对象的stopPropagation()方法便可。

1 function someHandle(event) {
2   event.stopPropagation();
3 }

因些,跨浏览器的中止事件传递的方法是:

1 function someHandle(event) {
2   event = event || window.event;
3   if(event.stopPropagation){
4     event.stopPropagation();
5   }else {
6     event.cancelBubble = true;
7   }
8 }

 

阻止事件的默认行为的处理方法

就像事件模型和事件对象差别同样,在IE和其它全部浏览器中阻止事件的默认行为的方法也不一样。

在IE下,经过设置event对象的returnValue为false便可。

1 function someHandle() {
2   window.event.returnValue = false;
3 }

DOM标准经过调用event对象的preventDefault()方法便可。

1 function someHandle(event) {
2   event.preventDefault();
3 }

因些,跨浏览器的取消事件传递后的默认处理方法是:

1 function someHandle(event) {
2   event = event || window.event;
3   if(event.preventDefault){
4     event.preventDefault();
5   }else{
6     event.returnValue = false;
7   }
8 }

完整的事件处理兼容性函数 

 1 var EventUtil = {
 2   addHandler: function(element, type, handler){
 3     if (element.addEventListener){
 4       element.addEventListener(type, handler, false);
 5     } else if (element.attachEvent){
 6       element.attachEvent("on" + type, handler);
 7     } else {
 8       element["on" + type] = handler;
 9     }
10   },
11   removeHandler: function(element, type, handler){
12     if (element.removeEventListener){
13       element.removeEventListener(type, handler, false);
14     } else if (element.detachEvent){
15       element.detachEvent("on" + type, handler);
16     } else {
17       element["on" + type] = null;
18     }
19   },
20   getEvent: function(event){
21     return event ? event : window.event;
22   },
23   getTarget: function(event){
24     return event.target || event.srcElement;
25   },
26   preventDefault: function(event){
27     if (event.preventDefault){
28       event.preventDefault();
29     } else {
30       event.returnValue = false;
31     }
32   },
33   stopPropagation: function(event){
34     if (event.stopPropagation){
35       event.stopPropagation();
36     } else {
37       event.cancelBubble = true;
38     }
39 };

 

捕获型事件模型与冒泡型事件模型的应用场合

标准事件模型为咱们提供了两种方案,可能不少朋友分不清这两种不一样模型有啥好处,为何不仅采起一种模型。
这里抛开IE浏览器讨论(IE只有一种,无法选择)什么状况下适合哪一种事件模型。

1. 捕获型应用场合

捕获型事件传递由最不精确的祖先元素一直到最精确的事件源元素,传递方式与操做系统中的全局快捷键与应用程序快捷键类似。当一个系统组合键发生时,若是注
册了系统全局快捷键监听器,该事件就先被操做系统层捕获,全局监听器就先于应用程序快捷键监听器获得通知,也就是全局的先得到控制权,它有权阻止事件的进
一步传递。因此捕获型事件模型适用于做全局范围内的监听,这里的全局是相对的全局,相对于某个顶层结点与该结点全部子孙结点造成的集合范围。

例如你想做全局的点击事件监听,相对于document结点与document下全部的子结点,在某个条件下要求全部的子结点点击无效,这种状况下冒泡模型就解决不了了,而捕获型却很是适合,能够在最顶层结点添加捕获型事件监听器,伪码以下:

1 function globalClickListener(event) {
2   if(canEventPass == false) {
3     //取消事件进一步向子结点传递和冒泡传递
4     event.stopPropagation();
5     //取消浏览器事件后的默认执行
6     event.preventDefault();
7   }
8 }

这样一来,当canEventPass条件为假时,document下全部的子结点click注册事件都不会被浏览器处理。

2. 冒泡型的应用场合

能够说咱们平时用的都是冒泡事件模型,由于IE只支持这模型。这里仍是说说,在恰当利用该模型能够提升脚本性能。在元素一些频繁触发的事件中,如
onmousemove,
onmouseover,onmouseout,若是明确事件处理后不必进一步传递,那么就能够大胆的取消它。此外,对于子结点事件监听器的处理会对父
层监听器处理形成负面影响的,也应该在子结点监听器中禁止事件进一步向上传递以消除影响。

 

综合案例分析

最后结合下面HTML代码做分析:

1 <body onclick="alert('current is body');">
2   <div id="div0" onclick="alert('current is '+this.id)">
3     <div id="div1" onclick="alert('current is '+this.id)">
4       <div id="div2" onclick="alert('current is '+this.id)">
5         <div id="event_source" onclick="alert('current is '+this.id)" style="height:200px;width:200px;background-color:red;"></div>
6       </div>
7     </div>
8   </div>
9 </body>

 

HTML运行后点击红色区域,这是最里层的DIV,根据上面说明,不管是DOM标准仍是IE,直接写在html里的监听处理函数是事件冒泡传递时调用的,由最里层一直往上传递,因此会前后出现
current is event_source
current is div2
current is div1
current is div0
current is body

添加如下片断:

1 var div2 = document.getElementById('div2');
2 EventUtil.addHandler(div2, 'click', function(event){
3   event = EventUtil.getEvent(event);
4   EventUtil.stopPropagation(event);
5 }, false);

current is event_source

current is div2

当点击红色区域后,根据上面说明,在泡冒泡处理期间,事件传递到div2后被中止传递了,因此div2上层的元素收不到通知,因此会前后出现:

在支持DOM标准的浏览器中,添加如下代码:

1 document.body.addEventListener('click', function(event){
2    event.stopPropagation();
3 }, true);

 

以上代码中的监听函数因为是捕获型传递时被调用的,因此点击红色区域后,虽然事件源是ID为event_source的元素,但捕获型选传递,从最顶层开始,body结点监听函数先被调用,而且取消了事件进一步向下传递,因此只会出现 current is body .

 

原文:http://www.cnblogs.com/binyong/articles/1750263.html

其余文章:

http://www.jb51.net/article/42492.htm

http://www.jb51.net/article/53438.htm

http://www.cnblogs.com/aji88/archive/2012/07/20/2600492.html

DOM简介:

http://baike.baidu.com/subview/14806/8904138.htm

http://www.w3school.com.cn/htmldom/dom_intro.asp

DOM事件流:http://baike.baidu.com/view/1148456.htm

事件类型:http://www.jb51.net/article/60124.htm

事件对象:http://www.jb51.net/article/60122.htm

相关文章
相关标签/搜索