事件javascript
HTML元素事件是浏览器内在自动产生的,当有事件发生时html元素会向外界(这里主要指元素事件的订阅者)发出各类事件,如click,onmouseover,onmouseout等等。html
DOM事件流java
DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所通过的结点都会收到该事件,这个传播过程可称为DOM事件流。浏览器
主流浏览器的事件模型函数
早在2004前在HTML元素事件的订阅,发送,传播,处理模型上各浏览器实现并不一致,直到DOM Level3中规定后,多数主流浏览器才陆陆续续支持DOM标准的事件处理模型 — 捕获型与冒泡型。
目前除IE浏览器外,其它主流的Firefox, Opera, Safari都支持标准的DOM事件处理模型。IE仍然使用本身的模型,即冒泡型,它模型的一部份被DOM采用,这点对于开发者来讲也是有好处的,只使用DOM标准,IE都共有的事件处理方式才能有效的跨浏览器。性能
冒泡型事件(Bubbling)this
这是IE浏览器对事件模型的实现,也是最容易理解的,至少笔者以为比较符合实际的。冒泡,顾名思义,事件像个水中的气泡同样一直往上冒,直到顶端。从DOM树型结构上理解,就是事件由叶子结点沿祖先结点一直向上传递直到根结点;从浏览器界面视图HTML元素排列层次上理解就是事件由具备从属关系的最肯定的目标元素一直传递到最不肯定的目标元素.操作系统
捕获型事件(Capturing)code
Netscape Navigator的实现,它与冒泡型恰好相反,由DOM树最顶层元素一直到最精确的元素,这个事件模型对于开发者来讲(至少是我..)有点费解,由于直观上的理解应该如同冒泡型,事件传递应该由最肯定的元素,即事件产生元素开始。
但这个模型在某些状况下也是颇有用的,接下来会讲解到。server
DOM标准事件模型
由于两个不一样的模型都有其优势和解释,DOM标准支持捕获型与冒泡型,能够说是它们二者的结合体。它能够在一个DOM元素上绑定多个事件处理器,而且在处理函数内部,this关键字仍然指向被绑定的DOM元素,另外处理函数参数列表的第一个位置传递事件event对象。
首先是捕获式传递事件,接着是冒泡式传递,因此,若是一个处理函数既注册了捕获型事件的监听,又注册冒泡型事件监听,那么在DOM事件模型中它就会被调用两次。
注册与移除事件监听器
注册事件监听器,或又称订阅事件,当元素事件发生时浏览器回调该监听函数执行事件处理。目前主流浏览器中有两种注册事件的方法,一种是IE浏览器的,另外一种是DOM标准的。
1.直接JS或HTML挂载法
<div onclick="alert(this.innerHTML);"> element.onclick = function(){alert(this.innerHTML);}
移除时将事件属性设为nul便可,这个也是最经常使用的方法了,优缺点也是显然的:
a.简单方便,在HTML中直接书写处理函数的代码块,在JS中给元素对应事件属性赋值便可
b. IE与DOM标准都支持的一种方法,它在IE与DOM标准中都是在事件冒泡过程当中被调用的。
c.能够在处理函数块内直接用this引用注册事件的元素
d.要给元素注册多个监听器,就不能用这方法了
2. IE下注册多个事件监听器与移除监听器方法
IE浏览器中HTML元素有个attachEvent方法容许外界注册该元素多个事件监听器,例如
element.attachEvent('onclick', observer);
attachEvent接受两个参数。第一个参数是事件名称,第二个参数observer是回调处理函数。这里得说明一下,有个常常会出错的地方,IE下利用attachEvent注册的处理函数调用时this指向再也不是先前注册事件的元素,这时的this为window对象了,笔者很奇怪IE为何要这么作,彻底看不出好处所在。
要移除先前注册的事件的监听器,调用element的detachEvent方法便可,参数相同。
element.detachEvent('onclick', observer);
3. DOM标准下注册多个事件监听器与移除监听器方法
实现DOM标准的浏览器与IE浏览器中注册元素事件监听器方式有所不一样,它经过元素的addEventListener方法注册,该方法既支持注册冒泡型事件处理,又支持捕获型事件处理。
element.addEventListener('click', observer, useCapture);
addEventListener方法接受三个参数。第一个参数是事件名称,值得注意的是,这里事件名称与IE的不一样,事件名称是没’on’开头的;第二个参数observer是回调处理函数;第三个参数注明该处理回调函数是在事件传递过程当中的捕获阶段被调用仍是冒泡阶段被调用
移除已注册的事件监听器调用element的removeEventListener便可,参数不变.
element.removeEventListener('click', observer, useCapture);
跨浏览器的注册与移除元素事件监听器方案
弄清楚DOM标准与IE的注册元素事件监听器之间的异同后,就能够实现一个跨浏览器的注册与移除元素事件监听器方案:
01.//注册 02.function addEventHandler(element, evtName, callback, useCapture) { 03. //DOM标准 04. if (element.addEventListener) { 05. element.addEventListener(evtName, callback, useCapture); 06. }else { 07. //IE方式,忽略useCapture参数 08. element.attachEvent('on' + evtName, callback); 09. } 10.} 11.//移除 12.//注册 13.function removeEventHandler(element, evtName, callback, useCapture) { 14. //DOM标准 15. if (element.removeEventListener) { 16. element.removeEventListener(evtName, callback, useCapture); 17. }else { 18. //IE方式,忽略useCapture参数 19. element.dettachEvent('on' + evtName, callback); 20. } 21.}
如何取消浏览器事件的传递与事件传递后浏览器的默认处理
先说明取消事件传递与浏览器事件传递后的默认处理是两个不一样的概念,可能不少同窗朋友分不清,或者根本不存在这两个概念。
取消事件传递是指,中止捕获型事件或冒泡型事件的进一步传递。例如上图中的冒泡型事件传递中,在body处理中止事件传递后,位于上层的document的事件监听器就再也不收到通知,再也不被处理。
事件传递后的默认处理是指,一般浏览器在事件传递并处理完后会执行与该事件关联的默认动做(若是存在这样的动做)。例如,若是表单中input type 属性是 “submit”,点击后在事件传播完浏览器就就自动提交表单。又例如,input 元素的 keydown 事件发生并处理后,浏览器默认会将用户键入的字符自动追加到 input 元素的值中。
要取消浏览器的件传递,IE与DOM标准又有所不一样。
在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 event.cancelBubble = true; 6.}
取消事件传递后的默认处理,IE与DOM标准又不所不一样。
在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 event.returnValue = false; 6.}
捕获型事件模型与冒泡型事件模型的应用场合
标准事件模型为咱们提供了两种方案,可能不少朋友分不清这两种不一样模型有啥好处,为何不仅采起一种模型。
这里抛开IE浏览器讨论(IE只有一种,无法选择)什么状况下适合哪一种事件模型。
1. 捕获型应用场合
捕获型事件传递由最不精确的祖先元素一直到最精确的事件源元素,传递方式与操做系统中的全局快捷键与应用程序快捷键类似。当一个系统组合键发生时,若是注册了系统全局快捷键监听器,该事件就先被操做系统层捕获,全局监听器就先于应用程序快捷键监听器获得通知,也就是全局的先得到控制权,它有权阻止事件的进一步传递。因此捕获型事件模型适用于做全局范围内的监听,这里的全局是相对的全局,相对于某个顶层结点与该结点全部子孙结点造成的集合范围。
例如你想做全局的点击事件监听,相对于document结点与document下全部的子结点,在某个条件下要求全部的子结点点击无效,这种状况下冒泡模型就解决不了了,而捕获型却很是适合,能够在最顶层结点添加捕获型事件监听器,伪码以下:
1.functionglobalClickListener(event) { 2. if(canEventPass == false) { 3.//取消事件进一步向子结点传递和冒泡传递 4.event.stopPropagation(); 5.//取消浏览器事件后的默认执行 6.event.preventDefault(); 7.} 8.}
这样一来,当canEventPass条件为假时,document下全部的子结点click注册事件都不会被浏览器处理。
2. 冒泡型的应用场合
能够说咱们平时用的都是冒泡事件模型,由于IE只支持这模型。这里仍是说说,在恰当利用该模型能够提升脚本性能。在元素一些频繁触发的事件中,如onmousemove, onmouseover,onmouseout,若是明确事件处理后不必进一步传递,那么就能够大胆的取消它。此外,对于子结点事件监听器的处理会对父层监听器处理形成负面影响的,也应该在子结点监听器中禁止事件进一步向上传递以消除影响。
综合案例分析
最后结合下面HTML代码做分析:
01.<body onclick="alert('current is body');"> 02. <div id="div0" onclick="alert('current is '+this.id)"> 03. <div id="div1" onclick="alert('current is '+this.id)"> 04. <div id="div2"> 05. <div id="event_source" 06. onclick="alert('current is '+this.id)" 07. style="height:200px;width:200px;background-color:red;"> 08. </div> 09. </div> 10. </div> 11. </div> 12.</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.addEventHandler(div2,'click',function(event){ 3. event = event || window.event; 4. if(event.stopPropagation) 5. event.stopPropagation(); 6. else event.cancelBubble = true; 7.},false);
当点击红色区域后,根据上面说明,在泡冒泡处理期间,事件传递到div2后被中止传递了,因此div2上层的元素收不到通知,因此会前后出现:
current is event_source
current is div2
在支持DOM标准的浏览器中,添加如下代码:
1.document.body.addEventListener('click',function(event){ 2. event.stopPropagation(); 3.},true);
以上代码中的监听函数因为是捕获型传递时被调用的,因此点击红色区域后,虽然事件源是ID为event_source的元素,但捕获型选传递,从最顶层开始,body结点监听函数先被调用,而且取消了事件进一步向下传递,因此只会出现current is body.