JavaScript——事件模型

时间:2023-03-09 14:28:21
JavaScript——事件模型

DOM事件流:

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

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

冒泡型事件(Event Bubbling)

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

捕获型事件(Event Capturing)

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

JavaScript——事件模型

DOM标准的事件模型

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

JavaScript——事件模型

事件句柄和事件接听器

事件句柄

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

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

如果在JavaScript 中分配事件处理函数, 则需要首先获得要处理的对象的一引用,然后将函数赋值给对应的事件处理函数属性,请看一个简单的例子:

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

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

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

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

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;"></div>
 6       </div>
 7     </div>
 8   </div>
 9 </body>
10 

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_sourcecurrent is div2

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

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

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

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