引言:
首先考虑这么一个问题,当在页面画很多同心圆,当你手机放在同心圆圆心的时候,你觉得你的手是放在所有圆内还是只是在你最近的那个圆。
那么还有一个问题,如果你觉得上面那个问题的答案是手指放在全部的圈内,那么你觉得应该怎么给圈排序的,是从最外面的圈开始还是从最近的圈开始呢?
一、事件流
其实说的问题就是接下来要说的事件流的问题了。当子节点和父节点都有点击事件,然后点击的是子节点,那么这时候先触发子节点还是父节点的事件呢?IE是这么想的,先触发子节点后触发父节点(冒泡型事件)。Netscape团队提出的来的却是先触发父节点(捕获型事件)。这就是事件流。
二、事件冒泡
IE事件流称为事件冒泡,即事件开始时由最具体的元素(文档中嵌层最深的那个节点)接受,然后逐次往上传播到较为不具体的节点(文档)。
<HTML XMLns="http://www.w3.org/1999/xHTML" lang="gb2312">
<head>
<title>title>
<meta name="keywords" content="JS,事件冒泡,cancelBubble,stopPropagation" />
<div id = "mydiv">Clickdiv>
body>
HTML>
比如我点击Click,那么事件会按照如下顺序传播:
div、body、html、document
三、捕获型事件
跟事件冒泡刚好相反,上述的例子顺序为:
document、html、body、div
事件捕获是很少人用的,冒泡用的比较多
四、DOM事件流(DOM2级事件)
DOM事件流有三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。其中事件捕获阶段不会接受事件
五、事件委托
既然说到了事件冒泡就说一个利用事件冒泡来做事件委托。
那么什么叫做事件委托呢?
对“事件处理程序过多”问题的解决方案就是事件委托,事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,例如click事件会一直冒泡到document层次,也就是说我们可以为整一个页面指定一个onclick事件处理程序,而不必为每一个可以单击的元素添加事件处理程序。
那么这么做有什么必要吗?
首先,添加到页面的事件处理程序数量直接关系到页面整体运行性能。原因一:每个函数都是一个对象,每一个对象都会占用内存,内存对象越多性能自然就差了。原因二:每次指定事件都需要访问dom节点,dom节点访问次数越多,会延迟整个页面的交互就绪事件。
看下面的一段代码:
<ul id="links">
<li id = "l1">1li>
<li id = "l2">2li>
<li id = "l3">3li>
ul>
<script>
var EventUtil = {
addHandler: function(element, type, handler){
//若浏览器支持addEventListener,则使用addEventListener来添加事件
if(element.addEventListener){ //DOM2级
element.addEventListener(type, handler, false);
} else if(element.attachEvent){ //IE
element.attachEvent("on" + type, handler);
} else {
//若以上两种添加事件的方法都不支持,则使用默认的行为来添加事件
element["on" + type] = handler; //DOM0
}
},
//移除事件
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;
}
},
getEvent :function (event) {
return event ? event : window.event;
},
getTarget :function (event) {
return event.target || event.srcElement;
},
stopPropagation: function (event) {
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
preventDefault: function (event) {
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
}
}
var links = document.getElementById('links');
EventUtil.addHandler(links, "click" ,function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id) {
case "l1" :
document.title = "Change the document title";
break;
case "l2" :
location.href = "http:www.baidu.com";
break;
case "l3" :
alert ('l3');
break;
}
});
这段代码实现了三个不同li节点的点击事件,但是只有一个事件处理程序就可以了,节省函数的同时也节省了访问节点的次数。(注意应该选取target节点作为对象)
Tips:
这里用到了一个EventUtil 对象来处理跨浏览器事件处理程序。
主要有三种不同的事件处理程序:
1、DOM0级事件处理程序
这是一种传统的事件处理程序,就是将一个函数赋值给一个事件处理程序属性。
例如:
//添加
var btn = document.getElementById("mybtn");
btn.onclick = function (){
alert(this.id);
}
//删除
btn.click = null;
2、DOM2级事件处理程序
使用addEventListener,removeEventListener指定或删除事件处理程序
//addEventListener(type,handler,false)
//removeEventListener(type,handler,false)
var btn = document.getElementById("mybtn");
var handler = function (){
alert(this.id);
}
btn.addEventListener = ('click',handler,false);
//删除
btn.removeEventListener= ('click',handler,false);
3、IE事件处理程序
使用attachEvent,detachEvent指定或删除事件处理程序
//attachEvent(type,handler)
//detachEvent(type,handler)
var btn = document.getElementById("mybtn");
var handler = function (){
alert(this.id);
}
btn.attachEvent= ('onclick',handler);
//删除
btn.detachEvent= ('onclick',handler);
事件对象event作为window对象的一个属性存在(DOM0和DOM2直接用event表示)
目标对象作为event中srcElement属性存在(DOM0和DOM2用event里面的target属性存在)
六、消除冒泡
可以用stopPropagation来消除冒泡行为,看一下经典例子
<HTML XMLns="http://www.w3.org/1999/xHTML" lang="gb2312">
<head>
<title> 阻止JS事件冒泡传递(cancelBubble 、stopPropagation)title>
<meta name="keywords" content="JS,事件冒泡,cancelBubble,stopPropagation" />
<script>
var EventUtil = {
addHandler: function(element, type, handler){
//若浏览器支持addEventListener,则使用addEventListener来添加事件
if(element.addEventListener){ //DOM2级
element.addEventListener(type, handler, false);
} else if(element.attachEvent){ //IE
element.attachEvent("on" + type, handler);
} else {
//若以上两种添加事件的方法都不支持,则使用默认的行为来添加事件
element["on" + type] = handler; //DOM0
}
},
//移除事件
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;
}
},
getEvent :function (event) {
return event ? event : window.event;
},
getTarget :function (event) {
return event.target || event.srcElement;
},
stopPropagation: function (event) {
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
preventDefault: function (event) {
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
}
}
function doSomething (obj,evt) {
alert(obj.id);
var e=(evt)?evt:window.event;
if (window.event) {
e.cancelBubble=true;// ie下阻止冒泡
} else {
//e.preventDefault();
EventUtil.stopPropagation(e);// 其它浏览器下阻止冒泡
}
}
script>
head>
<body>
<div id="parent1" onclick="alert(this.id)" style="width:250px;">
<p>This is parent1 div.p>
<div id="child1" onclick="alert(this.id)" style="width:200px;">
<p>This is child1.p>
div>
<p>This is parent1 div.p>
div>
<br />
<div id="parent2" onclick="alert(this.id)" style="width:250px;">
<p>This is parent2 div.p>
<div id="child2" onclick="doSomething(this,event);" style="width:200px;">
<p>This is child2. Will bubble.p>
div>
<p>This is parent2 div.p>
div>
body>
HTML>
结果:
当点击parent1的时候只触发parent1,当点击child1的时候parent1和child1都会触发
当点击parent2的时候只触发parent2,当点击child2的时候只触发child2
说明child2的冒泡被取消了