事件这个东西呢?是一个很神奇的东西,搞的我很烦 无论是在Android端还是前端。今天巧借这点时间呢?就准备来写一写这个东西。先写一篇前端的事件,然后再转向Android端写一篇Android端的事件 perfect...
一、 什么叫事件流
事件流的描述呢?很简单:就是描述从页面接收到事件的顺序。现今主流的两大阵营对此提出了两种相反的事件流:ie提出的称之为事件冒泡;Netscape communicator提出的则是事件捕获。那么什么是事件冒泡和事件捕获呢?
-
事件冒泡
IE的事件流叫做事件冒泡,说白了冒泡就是从嵌套最深的那个节点接收,然后逐级向上冒泡直到document的过程。用下面的html为例子:
我是测试
click Me
如果你单击了div元素,那么这个click事件就这么传递:
1、 div
2、 body
3、 html
4、document
也就是说,当我们点击了div元素之后,click事件首先在div元素上面发生,这个元素就是我们单击的元素。然后click事件就顺着DOM树向上传播,在每一级上面都会发生,一直传到document。
-
事件捕获
Netscape communicator团队提出了另一种事件流叫做事件捕获。事件捕获的过程呢?则是从document开始一直传递到div的过程:
1、document
2、 html
3、 body
4、 div
示意图如下:
-
DOM的事件流
通常的事件流包括三个步骤:事件捕获阶段、处于目标阶段和事件冒泡阶段。用前面的html表示,大概就是这样。
事件在处于捕获阶段的时候呢?是不会接收到事件的,然后再目标阶段的时候,事件发生在div元素中,然后通过冒泡传回文档流
二、 事件的处理程序
事件呢?就是:用户或者浏览器自身执行的某种动作。比如click,load和mouseover。而相应某个事件的函数叫做事件处理程序。一般都是on
开头的,onclick就是click的处理程序、onload就是load的处理程序等等...
首先 我们直接用代码来写主要的几种类型:
1、 html事件处理程序:
function showMessage(){
alert("hello my friends!")
}
2、 Dom0事件处理程序:
var btn2 = document.getElementById("myBtn2");
btn.onclick = function(){
alert("hello my friends!");
}
btn.onclick = null; //移除事件
3、 Dom2事件处理程序:
这里和前面不同,dom2定义了两个方法:addEventListener()和removeEventListener()分别用来绑定和删除事件。所有的dom节点都包含这个方法,并且它接受三个参数:处理的事件名、作为事件处理的函数和一个布尔值。如果这个布尔值是true,表示在捕获阶段调用事件程序;如果是false,则表示在冒泡阶段调用事件处理程序。
事件绑定如下:
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert("hello klivitam!")
},false)
关于事件绑定和移除有这么几个要注意的点:
- 事件多次绑定
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert("hello klivitam!")
},false)
btn.addEventListener("click",function(){
alert("123456")
},false)
如果重复进行添加事件的话,这样会先出按照顺序 先显示hello klivitam
,然后再显示123456
。
- 事件移除
先看下面一段代码
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert("hello klivitam!")
},false)
btn.removeEventListener("click",function(){
alert("hello klivitam!")
},false)
我们知道:利用addEventListener()绑定的事件,需要用removeEventListener()来将事件的监听移除,并且removeEventListener()的参数必须和addEventListener()的参数相同。这里我们虽然看似两者是相同的,但是并不能移除。这是值得注意的,针对这种情况,我们应该这么写:
var handle = function(){
alert("hello klivitam!")
}
var btn = document.getElementById("myBtn");
btn.addEventListener("click",handle,false)
btn.removeEventListener("click",handle,false)
3、 IE事件处理程序:
ie中存在和dom中想同的两个方法:attachEvent和detachEvent。这两个方法接受想同的两个参数:事件处理程序的名称与事件处理程序函数。
首先我们先写一个绑定的实例:
var btn2 = document.getElementById("myBtn2");
btn2.attachEvent("onclick",function(){
alert(window === this);
})
btn2.attachEvent("onclick",function(){
alert("boy");
})
同样和dom2的一样的操作,这里只不过函数名变了。这里值得注意的是:window和this相等
。当点击btn2元素的时候,首先会提示true,然后再提示boy。并且在移除代码的时候,和dom2的操作一样
var btn2 = document.getElementById("myBtn2");
var handle = function(){
alert("hello klivitam!")
}
btn2.attachEvent("onclick",handle)
btn2.detachEvent("onclick",handle)
4、 跨浏览器的事件处理程序:
前面说了这么多方面的事件处理程序,那么我们在进行开发的时候很可能会需要适配很多浏览器。我们在每次事件绑定的时候,肯定要考虑到诸多版本,诸多内核的浏览器。为了解决这种逻辑,我们就可以进行封装处理。我在这里是这样进行处理的:
var EventUtils = {
addHandler:function(ele,type,hander){
if(ele.addEventListener){
ele.addEventListener(type,hander,false);
}else if(ele.attachEvent){
ele.attachEvent("on"+type,hander);
}else{
ele["on"+type] = hander;
}
}
removeHandler:function(ele,type,hander){
if(ele.removeEventListener){
ele.removeEventListener(type,hander,false);
}else if(ele.detachEvent){
ele.detachEvent("on"+type,hander);
}else{
ele["on"+type] = null;
}
}
}
首先,我们在绑定事件的时候呢?绑定事件的时候首先判断是否支持dom2的事件处理程序,如果不支持,那么继续判断是否支持ie的事件处理程序,如若还不支持就只能用通用的事件处理程序。解绑的时候也是一样,先判断是否支持dom2事件处理程序,接着判断ie的事件处理程序,最后在都不支持的情况下使用通用的事件处理程序。
我们在使用的时候就可以这样写一下就好了:
var btn2 = document.getElementById("myBtn2");
var handle = function(){
alert("hello klivitam!");
}
EventUtils.addHandler(btn2,"click",hander);
EventUtils.removeHandler(btn,"click",hander);
当然这个封装还室友点瑕疵的,比如不知道this的指向等等,当然 我在后续的文章中会继续的去讨论封装这个组件的。
三、 事件的对象
我们知道在触发dom上的某个事件对象的时候,这个对象会包含着所有与事件有关的信息。包括事件的元素、事件的类型以及其他与特定事件相关的信息。
-
dom的事件对象
兼容dom的浏览器会将一个event对象传到事件处理程序中。例如:
btn.addEventListener("click",function(e){
console.log(e);
},false)
btn.onclick = function(e){
console.log(e);
};
上图是我打印出来传递的event对象中所包含的值。我在这里呢?就简单的介绍几个最基本的含义,剩下的其实大多数大家都能看懂。
属性/方法 | 类型 | 读写 | 说明 |
---|---|---|---|
bubbles | boolean | 只读 | 表明事件是否冒泡 |
cancelable | boolean | 只读 | 表明是否可以取消事件的默认行为 |
currentTarget | Element | 只读 | 其事件的处理程序正在处理程序的那个元素 |
defaultPrevented | boolean | 只读 | 为true表示已经调用了preventDefault(dom3新增的) |
detail | Integer | 只读 | 与事件相关的详细细节 |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1表示捕获,2处于目标,3处于冒泡 |
preventDefault() | Function | 只读 | 取消事件的 默认行为,如果cancelable为true才能使用这个方法 |
stopImmediatePropagation | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(dom3新增) |
stopPropagation | Function | 只读 | 取消事件的进一步捕获或者冒泡。如果cancelable为true的时候,才能使用这个方法 |
target | Element | 只读 | 事件的目标 |
trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示事件又开发者通过js创建的(dom3新增的) |
type | String | 只读 | 被触发事件的类型 |
view | abstractview | 只读 | 与事件相关联的抽象视图,等同于发生事件的window对象 |
上面表格中值得注意三点的就是:
this是始终等于currentTarget的值,而target则只包含事件的实际目标
当cancalable为true的时候,我们可以通过preventDefault函数来取消其默认行为
eventPhase可以用来判断事件的状态,捕获状态为1,目标对象上为2,冒泡上为3 。值得注意的是,当eventPhase为2的时候this,target,currentTarget是始终相等的。
-
ie的事件对象
和访问dom中的event不同,ie中访问event对象有几种不同的方式。来看下面的例子
btn.onclick = function(){
var e = window.event;
console.log(e);
};
btn.attachEvent("onclick",function(e){
console.log(e)
})
同样我也将ie事件的主要的几个属性也列在下面
属性/方法 | 类型 | 读写 | 说明 |
---|---|---|---|
cancelBubble | boolean | 读/写 | 默认值为false,但将其设置为true就可以取消事件(和dom中的stopPropagation()方法作用相同) |
returnValue | boolean | 读写 | 默认值为true,但将其设置为false就可以取消事件默认行为(跟dom中的preventDefault()方法的作用相同) |
srcElement | element | 只读 | 事件的目标(雷同dom中的target) |
type | String | 只读 | 被触发的事件的类型 |
上面也值得我们注意的几点是:
- 在事件处理程序中(dom中),srcElemet属性等于this,但是attachEvent this指向的是window
btn.onclick = function(){
console.log(window.event.srcElement === this); // true
};
btn.attachEvent("onclick",function(e){
console.log(e.srcElement === this) //false
})
由前面表格所说returnValue属性相当于DOM中的preventDefault()方法,能取消事件的默认行为。在ie中,只要将returnValue设置成false,就可以组织默认行为了。
-
跨浏览器的事件对象
前面仅仅封装了事件的绑定和解绑,但是我们考虑到我们有的时候可能会用到事件的event对象,前面的介绍可以知道ie和dom中会有些许不同,此时我们就可以对事件进行再次封装。代码如下:
var EventUtils = {
addHandler:function(ele,type,hander){
if(ele.addEventListener){
ele.addEventListener(type,hander,false);
}else if(ele.attachEvent){
ele.attachEvent("on"+type,hander);
}else{
ele["on"+type] = hander;
}
}
getEvent:function(e){
return e?e:window.event;
}
getTarget:function(e){
return e.target||e.srcElement;
}
preventDefault:function(e){
if(e.preventDefault){
e.preventDefault()
}else{
e.returnValue = false;
}
}
removeHandler:function(ele,type,hander){
if(ele.removeEventListener){
ele.removeEventListener(type,hander,false);
}else if(ele.detachEvent){
ele.detachEvent("on"+type,hander);
}else{
ele["on"+type] = null;
}
}
stopPropagation:function(e){
if(e.stopPropagation){
e.stopPropagation();
}else{
e.cancelBubble = true;
}
}
}
这里呢?主要总结了事件冒泡的一些跨浏览器的兼容性问题,当然值得注意的是IE 不支持事件捕获的,所以这个方法不是不支持事件捕获的。
说在最后
十一呢?去过漂流,也去爬山,然后腿也瘸了。之后在家看了几天的书籍。怎么说呢?领悟到活着的好处,也领悟到活蹦乱跳的快乐了。我已经决心不在做一个秀才,突然有个瞬间觉得这个城市这么大 我却没去过几处的感觉。
嗯!关于接下来几个月的规划呢?我会每月写一篇书评,每周写一篇Android,写一篇前端、写一篇小程序的文章来鞭策自己不断的向前。敬请期待吧