开门见山
一、何为事件流:当页面元素触发事件的时候,该元素的容器以及整个页面都会按照特定顺序响应该元素的触发事件,事件传播的顺序叫做事件流程。
事件流主要分为两种:
1.冒泡型事件(所有浏览器都支持)
由明确的事件源到最不确定的事件源依次向上触发
2.捕获型事件(IE不支持)
由不确定的事件源到明确的事件源一次向下触发。
3.DOM事件流
如图,W3c的DOM事件触发分为三个阶段:
①、事件捕获阶段,即由最顶层元素(一般是从window元素开始,有的浏览器是从document开始)开始,逐次进入dom内部,最后到达目标元素,依次执行绑定在其上的事件
②、处于目标阶段,检测机制到达目标元素,按事件注册顺序执行绑定在目标元素上的事件。
③、事件冒泡阶段,从目标元素出发,向外层元素冒泡,最后到达顶层(window或document),依次执行绑定再其上的事件。
事件捕获阶段:实际目标(div)在捕获阶段不会接收事件。也就是在捕获阶段,事件从window到document再到body就停止了。
处于目标阶段:事件在div上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。
冒泡阶段:事件又传播回文档。
二、事件处理程序:响应某个事件的函数就叫做事件处理程序。
因此,click对应的时间处理程序就是 “onclick”。给事件指定事件处理程序有好几种方式。
1.html方式
<div class="zuzhi">
<button type="button" onclick="alert('html方式')">html方式</button>
</div>
或者在html页面里面直接添加函数名称
<script>
function a(){
alert('html方式')
}
</script>
<div class="zuzhi">
<button type="button" onclick="a()">html方式</button>
</div>
使用html事件处理程序的缺点:
1. 不符合HTML内容和JavaScript事件分离的原则,应该尽量避免这种添加事件处理函数的方式。
2. 存在时差问题,上面第二种方式,如果函数定义在按钮下面,页面底部定义的,如果用户在页面解析shoumessage函数之前单击按钮,就会引发错误。
3. html和JavaScript耦合度太高,如果想要修改事件处理程序,就要同时修改html代码和JavaScript代码。
基于上述原因呢,我们一般不适用html方式的事件处理程序,而使用DOM事件处理程序
2.DOM事件处理程序
目前DOM事件处理程序:DOM0级方式,DOM2级方式,和IE处理方式。
(1)DOM0级事件处理程序
DOM0级的事件处理程序很简单,onclick就是常用的DOM0级事件处理函数,只会在冒泡阶段被处理。
<div class="zuzhi"></div>
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
zvzhi.onclick=function(){
alert("DOM0级处理程序方式");
};
</script>
这是我们经常用到的一种方式,以这种方式添加的事件处理程序将会在事件冒泡阶段被处理。
同样也可以删除通过DOM0级方法指定的事件处理程序,只要将其设置为 null 即可:
zvzhi.onclick=null;
使用DOM0级事件处理程序的优点:
浏览器兼容性较好,早期的浏览器都能完美支持这种方式。
使用DOM0级事件处理程序最大的缺点:
在于为同一个对象指定多个事件处理程序的时候,后面的函数将会覆盖前面的。
如下例:
<div class="zuzhi"></div>
</body>
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
zvzhi.onclick=function(){
alert("方法1")
};
zvzhi.onclick=function(){
alert("方法2")
};
</script>
点击div的时候只alert(“方法2”),不急,且看DOM2级事件处理程序
(2)DOM2级事件处理程序
前面也说了,DOM事件流分为3个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。
大部分浏览器默认的都是事件源冒泡流的方式来响应事件(自然是在事件冒泡阶段做出响应),不过浏览器也都支持“事件捕获流”的方式(支持在捕获阶段响应事件),需要我们手动设置,怎么设置?
不急,DOM2级事件处理程序定义了两个方法:
addEventLister 用于添加事件处理程序
removeEventLister 用于删除事件处理程序
分别接受三个参数:事件名字,事件处理程序,布尔值(为true代表捕获阶段,false代表冒泡阶段)。
例如: button.addEventListener(“click”,function( ){….},false);
将第三个参数设置为true,就表示在“捕获阶段”响应事件,此时事件流就是从外向里流的(先响应body上的事件,在响应div上的,最后响应button上的事件;反之则表示在“事件冒泡阶段”响应事件。
例如:
<div class="zuzhi"></div>
</body>
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法")};
function fun2(){alert("这是body的方法")};
zvzhi.addEventListener("click",fun1,false);
bosy.addEventListener("click",fun2,false);
</script>
因为第三个布尔值的参数设为false,所以在事件捕获阶段响应事件,点击div的时,页面一次:
alert(“这是div的方法”)
alert(“这是body的方法”)
把第三个参数改为:true;点击div时弹出的顺序相反;
奇迹出现了,前面我们提到使用DOM0级事件处理的onclick的话,后面的会覆盖前面的,但是DOM2级不会,会一次执行
<div class="zuzhi"></div>
</body>
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法1")};
function fun2(){alert("这是div的方法2")};
zvzhi.addEventListener("click",fun1,false);
zvzhi.addEventListener("click",fun2,false);
</script>
点击DIV会依次弹出:
alert(“这是div的方法1”)
alert(“这是div的方法2”)
我们再看removeEventLister 方法
removeEventListener() 方法用于移除由addEventListener() 方法添加在dom元素上的的事件句柄。
<div class="zuzhi"></div>
</body>
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法")};
function fun2(){alert("这是body的方法")};
zvzhi.addEventListener("click",fun1,false);
bosy.addEventListener("click",fun2,false);
zvzhi.removeEventListener("click",fun1,false);
</script>
由于div上面的fun1方法已经被移除,所以点击div只alert(“这是body的方法”);
<script>
var zvzhi=document.getElementsByClassName('zuzhi')[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法")};
function fun2(){alert("这是body的方法")};
zvzhi.addEventListener("click",fun1,false);
bosy.addEventListener("click",fun2,false);
zvzhi.removeEventListener("click",fun1,true);
</script>
反之,把第三个参数改为true;点击div的时候,,依然弹出:
alert(“这是div的方法”)
alert(“这是body的方法”)
说明div上面绑定的事件fun1并没有被移除;
第三个布尔值的意义:
可选。布尔值,指定移除事件句柄的阶段。
可能值:
true - 事件句柄在捕获阶段移除
false- 默认。事件句柄在冒泡阶段移除
注意: 如果添加两次事件句柄,一次在捕获阶段,一次在冒泡阶段,你必须单独移除该事件。
(3)IE事件处理程序方式:
IE不同的它有自己的方法attachEvent()和detachEvent,这两个接受相同的两个参数:事件处理程序名称和事件处理程序函数。由于IE8及其以下版本的浏览器只支持事件冒泡,所以用这个方法添加的事件都会在冒泡阶段被处理,所以不需要第三个参数。
<div class="zuzhi"></div>
</body>
<script>
if(!document.getElementsByClassName){
document.getElementsByClassName = function(className, element){
var children = (element || document).getElementsByTagName('*');
var elements = new Array();
for (var i=0; i<children.length; i++){
var child = children[i];
var classNames = child.className.split(' ');
for (var j=0; j<classNames.length; j++){
if (classNames[j] == className){
elements.push(child);
break;
}
}
}
return elements;
};
}
var zvzhi=document.getElementsByClassName("zuzhi")[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法1")};
function fun2(){alert("这是div的方法2")};
zvzhi.attachEvent('onclick',fun1);
bosy.attachEvent('onclick',fun2);
页面点击div的结果是:先alert(“这是div的方法1”)再alert(“这是div的方法2”)
因为IE不支持document.getElementsByClassName所以需要把前面那段代码进行粘贴,用于IE类名的获取。
在IE中给统一元素添加多个点击事件
var zvzhi=document.getElementsByClassName("zuzhi")[0];
var bosy=document.getElementsByTagName("body")[0];
function fun1(){alert("这是div的方法1")};
function fun2(){alert("这是div的方法2")};
zvzhi.attachEvent('onclick',fun1);
zvzhi.attachEvent('onclick',fun2);
点击div执行的顺序是:alert(“这是div的方法2”) alert(“这是div的方法1”)
与DOM2级执行的顺序相反,从后往前
IE事件处理程序方式与DOM2事件处理程序方的不同:
1.最大的不同是方法名的不同,
DOM2级:addEventListener() 和 removeEventListener()
IE级:attachEvent()和detachEvent()
2.事件名添加的不同,DOM2只需要click而IE需要on前缀
3.IE不支持事件捕获,所以没有第三个参数
4.IE中多个点击事件,事件执行顺序是从后往前的
5.事件处理函数的作用域为全局window
var btn = document.getElementById("button");
btn.attachEvent("onclick",function(){
console.log(this); //返回window对象
})
删除事件:
zvzhi.detachEvent('onclick',fun1);
那么,我们在开发过程中,可能要绑定事件处理程序要同时兼容IE和其他高端浏览器,怎么办,看下面代码:
以下是《JavaScript高级程序设计(第3版)》给出的代码:
var EventUtil = {
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false); //DOM2级
}else if(element.attachEvent){
element.attachEvent("on"+type,handler); //IE
}else{
element["on"+type] = handler; //DOM0级
}
},
removeHandler:function(element,type,handler){
if(element.removeEventListener){
element.removeEventListener(type,handler,false); //DOM2
}else if(element.detachEvent){
element.detachEvent("on"+type,handler); //IE
}else{
element["on"+type] = null; //DOM0
}
}
}
使用方法如下:
var btn = document.getElementById("button");
var handler = function(){
alert(1);
}
EventUtil.addHandler(btn,"click",handler); //添加事件
EventUtil.removeHandler(btn,"click",handler); //移除事件
以上代码只能冒泡阶段进行事件响应;
三阻止事件流
*为什么要阻止事件流:*
某个DOM节点绑定了某事件监听器,本来是想当该DOM节点触发事件,才会执行回调函数。结果是该节点的某后代节点触发某事件,由于事件冒泡,该DOM节点事件也会触发,执行了回调函数,这样就违背了最初的本意了。当出现这种情况时,我们就必须要阻止事件流。
阻止事件流分为:1. 阻止默认时间流; 2. 阻止冒泡事件流
(1)阻止默认事件流
preventDefault() 方法阻止元素发生默认的行为(例如,当点击提交按钮时阻止对表单的提交)。
防止链接打开 URL:
$("a").click(function(event){
event.preventDefault();
});
我尝试了这种DOM0级的事件处理程序的方式,点击a连接的时候还是可以跳转;
var baidu=document.getElementById('baidu');
baidu.addEventListener("click",function(e){
e.preventDefault();
},false);
使用这种DOM2级的事件处理程序的方式是可以的
解释:点击链接的时候正常情况下会发生跳转,但是现在我们阻止了它的默认事件,即跳转事件,这时就不会跳转了。
不过 preventDefault() 是W3C标准下的方法,IE不支持;
下面代码为兼容IE的阻止已浏览器默认行为:
var preventDefault = function(e) {
e = e || window.event;
if(e.preventDefault) {
e.preventDefault(); //W3C
}else{
e.returnValue = false; //IE
}
}
(2)阻止冒泡事件
/*阻止冒泡事件*/
var stopPropagation =function(e){
var event=e||window.event;
if(!+"\v1"){ //JS代码中用来判断当前浏览器是不是IE浏览器的简单有效的方法
event.cancelBubble=true;
}else{
event.stopPropagation()
}
};
使用方法:
var baidu=document.getElementById('baidu');
baidu.addEventListener("click",function(e){
preventDefault();
stopPropagation();
},false);
(3)同时阻止默认和冒泡
阻止事件(包括冒泡和默认行为)
var stopEvent = function(e){
e = e || window.event;
if(e.preventDefault) {
e.preventDefault(); //W3C标准阻止浏览器默认
e.stopPropagation(); //W3C标准阻止冒泡事件流
}else{
e.returnValue = false; //IE标准阻止浏览器默认
e.cancelBubble = true; //IE标准阻止冒泡事件流
}
};
总结:后续补上。