JavaScript事件流

部分转载 http://cube.qq.com/?p=328
http://www.cnblogs.com/webflash/archive/2009/08/23/1552462.html

事件流

事件流是描述从页面中接受事件的顺序,ie实现的是事件冒泡流,而Netscape实现的是事件捕获流
而现代的Dom事件流包括了三个阶段:事件捕获、处于目标阶段和时间冒泡阶段,如下图所示
Alt text;

ie9 opera firefox chrome safari 都支持dom流,而ie8之前不支持

Dom0 Dom2 Dom3

DOM0

直接通过 onclick写在html里面的事件, 比如:

<input onclick="alert(1)" />

使用Dom0级方法指定的事件处理程序被认为是元素的方法
即程序中的this引用的是当前元素

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id); //"myBtn"
}

删除Dom0的事件处理程序

btn.onclick = null;

DOM2

是通过addEventListener绑定的事件, 还有IE下的DOM2事件通过attachEvent绑定;

var btn = document.getElementById("myBtn");
btn.addEventListener = ('click',function(){
    alert(this.id); //"myBtn"
},false);

如果该方法传入的最后一个参数值为true,表示事件处理程序被注册在捕获阶段,如果为false表示件处理程序被注册在冒泡阶段。

DOM3

是一些新的事件, 区别DOM3和DOM2的方法我感觉是DOM3事件有分大小写的,DOM2没有;

事件流的作用

允许多个操作被集中处理(把事件处理器添加到一个父级元素上,避免把事件处理器添加到多个子级元素上),它还可以让你在对象层的不同级别捕获事件。

<div onclick="eventHandle(event)" id="outSide" style="width:100px; height:100px; background:#000; padding:50px">
    <div id="inSide" style="width:100px; height:100px; background:#CCC"></div>
</div>
<script type="text/javascript">
//本例子只在外面盒子定义了处理方法,而这个方法一样可以捕获到子元素点击行为并处理它。假设有成千上万子元素要处理,难道我们要为每个元素加“onclick="eventHandle(event)"”?显然没有这种集中处理的方法来的简单,同时它的性能也是更高的。
function eventHandle(e)
{
    var e=e||window.event;
    var obj=e.target||e.srcElement;
    alert(obj.id+' was click')
}
</script>

让不同的对象同时捕获同一事件,并调用自己的专属处理程序做自己的事情

<div onclick="outSideWork()" id="outSide" style="width:100px; height:100px; background:#000; padding:50px">
    <div onclick="inSideWork()" id="inSide" style="width:100px; height:100px; background:#CCC"></div>
</div>
<script type="text/javascript">
function outSideWork()
{
    alert('My name is outSide,I was working...');
}

function inSideWork()
{
    alert('My name is inSide,I was working...');
}

//因为下面程序自动激活单击事件,有些浏览器不允许,所以请单击灰色盒子,从这里开始下命令,这样因为冒泡的原因,黑色大盒子也会收到单击事件,并调用了自己的处理程序。如果还有更多盒子嵌套,一样道理。
</script>

事件处理顺序

在W3C事件模型中,任何事件会首先被捕获直至到达目标元素然后再冒泡回去。事件流包括3个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。Web开发者可以选择将事件处理程序注册在捕获或者冒泡阶段。这可以通过addEventListener()方法来实现。(element2是element1的子元素)

element1.addEventListener(“click”, doSomething2, true);
element2.addEventListener(“click”, doSomething, false);

如果用户点击元素2会发生如下事情:

  • 点击事件开始于捕获阶段。它会先查询是否有元素2的任何祖先元素在捕获阶段绑定了onclick事件。
  • 它发现祖先元素1在捕获阶段绑定了onclick事件,于是element1.doSomething2()首先被执行。
  • 事件一直查询到目标元素element2都没有再发现别的在捕获阶段绑定的onclick事件,事件转到它的冒泡阶段并执行doSomething()(注册在element2上的在冒泡阶段执行的事件处理程序)。
  • 事件再次向上查询并检查是否有任何祖先元素在冒泡阶段绑定了onclick事件,并没有查询到,所以什么都没有发生。

再看相反的例子:

element1.addEventListener(“click”, doSomething2, false);
element2.addEventListener(“click”, doSomething, false);

现在如果用户点击元素2,下面的事情会按顺序发生:

  • 点击事件发生于捕获阶段。事件查询元素2是否有任何祖先元素在捕获阶段绑定了onclick事件并且没有查找到这样的元素。
  • 事件查询到目标元素element2自己。事件转为冒泡阶段并执行doSomething()。
  • 事件再次向上查询并检查目标元素是否有任何祖先元素在冒泡阶段绑定了onclick事件。
  • 它找到了满足条件的元素1,然后执行doSomething2()。

始终都会发生

首先需要理解的是事件捕获或者冒泡始终都会发生。如果你为整个document定义一个onclick事件处理程序:

document.onclick = function(){
  alert("clicked");
};

在文档中任何元素上的任何点击事件最终都会冒泡到document,并且触发上面注册的事件处理程序,从而弹出一个警告框。只有当之前的事件处理程序阻止冒泡时,才不会冒泡到document上。

如果你的文档结构非常复杂,你可以通过阻止冒泡来节约系统资源。毕竟,浏览器必须查询事件目标的每一个祖先元素是否有绑定相同的事件,即使一个也没有找到,这个查询的过程仍然需要花费时间。

要想阻止冒泡,在Microsoft模型中,你需要将事件的cancelBubble属性设置为true。在W3C模型中,你需要调用事件的stopPropagation()方法。

阻止冒泡

如果想解决浏览器兼容问题,你可以像下面这样写:

function doSomething(e){
  if(!e){
    var e = window.event;
  }
  e.cancelBubble = true;
  if(e.stopPropagation){
    e.stopPropagation();
  }
}

在不支持cancelBubble属性的浏览器中设置它的值并不会报错。浏览器会忽略它并创建这个属性。当然,它并不能真正地阻止冒泡,但是给它分配值的操作本身是安全的

currentTarget

就像我们之前看到的,一个事件拥有一个target或者srcElement属性,它包含一个对事件所发生的元素的引用。在我们的例子中是element2,因为用户点击的是它。

理解在捕获和冒泡过程中这个target是不会改变的是非常重要的:它始终是对element2的引用。

但是假设我们像下面这样注册这些事件处理程序:

element1.onclick = doSomething;
element2.onclick = doSomething;

如果用户点击element2,doSomething()会执行两次。但是你怎样知道哪一个HTML元素正在处理这个事件?target/srcElement并不能给出正确答案,因为它们始终是对element2的引用。

为了解决这个问题,W3C添加了currentTarget属性。它包含对正在处理事件的HTML元素的引用。不幸的是Microsoft模型并不支持类似的属性。

你也可以使用this关键字。在上面的例子中它引用正在处理事件的HTML元素,就像currentTarget属性。

注意要点

  • 不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。

  • 事件捕获方式在不同浏览器,甚至同种浏览器的不同版本中是有所区别的。如Netscape4.0采用捕获型事件解决方案,其它多数浏览器则支持冒泡型事件解决方案,另外DOM事件流还支持文本节点事件冒泡。

  • 事件捕获到达顶层的目标在不同浏览器或不同浏览器版本也是有区别的。在IE6中HTML是接收事件冒泡的,另外大部分浏览器将冒泡延续到window对象,即……body→documen→window。

  • 阻止冒泡并不能阻止对象默认行为。比如submit按钮被点击后会提交表单数据,这种行为无须我们写程序定制。

实例

实践是检验真理的唯一标准,对于jquery,必须写在内部
实际用的过程,可能一个父元素有一个事件,子元素有另一个事件,为了保证点击子元素的时候,父元素不被触发,必须阻止冒泡

1
2
3
4
5
<ul>ul (祖父)
<li onclick=click2()>li (直接父)
<span onclick=click1()>span</span>
</li>
</ul>
1
2
3
4
5
6
7
8
9
10
$(function($) {
$("span").click(function() {
alert
event.stopPropagation();
});
});
function click2(){
alert("li");
}