事件這個(gè)東西呢?是一個(gè)很神奇的東西,搞的我很煩 無(wú)論是在Android端還是前端。今天巧借這點(diǎn)時(shí)間呢?就準(zhǔn)備來(lái)寫一寫這個(gè)東西。先寫一篇前端的事件,然后再轉(zhuǎn)向Android端寫一篇Android端的事件 perfect...
一、 什么叫事件流
事件流的描述呢?很簡(jiǎn)單:就是描述從頁(yè)面接收到事件的順序。現(xiàn)今主流的兩大陣營(yíng)對(duì)此提出了兩種相反的事件流:ie提出的稱之為事件冒泡;Netscape communicator提出的則是事件捕獲。那么什么是事件冒泡和事件捕獲呢?
-
事件冒泡
IE的事件流叫做事件冒泡,說(shuō)白了冒泡就是從嵌套最深的那個(gè)節(jié)點(diǎn)接收,然后逐級(jí)向上冒泡直到document的過(guò)程。用下面的html為例子:
<!DOCTYPE html>
<html>
<head>
<title>我是測(cè)試</title>
</head>
<body>
<div >click Me</div>
</body>
</html>
如果你單擊了div元素,那么這個(gè)click事件就這么傳遞:
1、 div
2、 body
3、 html
4、document
也就是說(shuō),當(dāng)我們點(diǎn)擊了div元素之后,click事件首先在div元素上面發(fā)生,這個(gè)元素就是我們單擊的元素。然后click事件就順著DOM樹向上傳播,在每一級(jí)上面都會(huì)發(fā)生,一直傳到document。

-
事件捕獲
Netscape communicator團(tuán)隊(duì)提出了另一種事件流叫做事件捕獲。事件捕獲的過(guò)程呢?則是從document開始一直傳遞到div的過(guò)程:
1、document
2、 html
3、 body
4、 div
示意圖如下:

-
DOM的事件流
通常的事件流包括三個(gè)步驟:事件捕獲階段、處于目標(biāo)階段和事件冒泡階段。用前面的html表示,大概就是這樣。

事件在處于捕獲階段的時(shí)候呢?是不會(huì)接收到事件的,然后再目標(biāo)階段的時(shí)候,事件發(fā)生在div元素中,然后通過(guò)冒泡傳回文檔流
二、 事件的處理程序
事件呢?就是:用戶或者瀏覽器自身執(zhí)行的某種動(dòng)作。比如click,load和mouseover。而相應(yīng)某個(gè)事件的函數(shù)叫做事件處理程序。一般都是on開頭的,onclick就是click的處理程序、onload就是load的處理程序等等...
首先 我們直接用代碼來(lái)寫主要的幾種類型:
1、 html事件處理程序:
<button id="myBtn1" onclick="showMessage()">我是 html事件處理程序</button>
function showMessage(){
alert("hello my friends!")
}
2、 Dom0事件處理程序:
<button id="myBtn2">我是dom0事件處理程序</button>
var btn2 = document.getElementById("myBtn2");
btn.onclick = function(){
alert("hello my friends!");
}
btn.onclick = null; //移除事件
3、 Dom2事件處理程序:
這里和前面不同,dom2定義了兩個(gè)方法:addEventListener()和removeEventListener()分別用來(lái)綁定和刪除事件。所有的dom節(jié)點(diǎn)都包含這個(gè)方法,并且它接受三個(gè)參數(shù):處理的事件名、作為事件處理的函數(shù)和一個(gè)布爾值。如果這個(gè)布爾值是true,表示在捕獲階段調(diào)用事件程序;如果是false,則表示在冒泡階段調(diào)用事件處理程序。
事件綁定如下:
<button id="myBtn">我是dom2事件處理程序</button>
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert("hello klivitam!")
},false)
關(guān)于事件綁定和移除有這么幾個(gè)要注意的點(diǎn):
- 事件多次綁定
var btn = document.getElementById("myBtn");
btn.addEventListener("click",function(){
alert("hello klivitam!")
},false)
btn.addEventListener("click",function(){
alert("123456")
},false)
如果重復(fù)進(jìn)行添加事件的話,這樣會(huì)先出按照順序 先顯示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()來(lái)將事件的監(jiān)聽移除,并且removeEventListener()的參數(shù)必須和addEventListener()的參數(shù)相同。這里我們雖然看似兩者是相同的,但是并不能移除。這是值得注意的,針對(duì)這種情況,我們應(yīng)該這么寫:
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中想同的兩個(gè)方法:attachEvent和detachEvent。這兩個(gè)方法接受想同的兩個(gè)參數(shù):事件處理程序的名稱與事件處理程序函數(shù)。
首先我們先寫一個(gè)綁定的實(shí)例:
var btn2 = document.getElementById("myBtn2");
btn2.attachEvent("onclick",function(){
alert(window === this);
})
btn2.attachEvent("onclick",function(){
alert("boy");
})
同樣和dom2的一樣的操作,這里只不過(guò)函數(shù)名變了。這里值得注意的是:window和this相等。當(dāng)點(diǎn)擊btn2元素的時(shí)候,首先會(huì)提示true,然后再提示boy。并且在移除代碼的時(shí)候,和dom2的操作一樣
var btn2 = document.getElementById("myBtn2");
var handle = function(){
alert("hello klivitam!")
}
btn2.attachEvent("onclick",handle)
btn2.detachEvent("onclick",handle)
4、 跨瀏覽器的事件處理程序:
前面說(shuō)了這么多方面的事件處理程序,那么我們?cè)谶M(jìn)行開發(fā)的時(shí)候很可能會(huì)需要適配很多瀏覽器。我們?cè)诿看问录壎ǖ臅r(shí)候,肯定要考慮到諸多版本,諸多內(nèi)核的瀏覽器。為了解決這種邏輯,我們就可以進(jìn)行封裝處理。我在這里是這樣進(jìn)行處理的:
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;
}
}
}
首先,我們?cè)诮壎ㄊ录臅r(shí)候呢?綁定事件的時(shí)候首先判斷是否支持dom2的事件處理程序,如果不支持,那么繼續(xù)判斷是否支持ie的事件處理程序,如若還不支持就只能用通用的事件處理程序。解綁的時(shí)候也是一樣,先判斷是否支持dom2事件處理程序,接著判斷ie的事件處理程序,最后在都不支持的情況下使用通用的事件處理程序。
我們?cè)谑褂玫臅r(shí)候就可以這樣寫一下就好了:
var btn2 = document.getElementById("myBtn2");
var handle = function(){
alert("hello klivitam!");
}
EventUtils.addHandler(btn2,"click",hander);
EventUtils.removeHandler(btn,"click",hander);
當(dāng)然這個(gè)封裝還室友點(diǎn)瑕疵的,比如不知道this的指向等等,當(dāng)然 我在后續(xù)的文章中會(huì)繼續(xù)的去討論封裝這個(gè)組件的。
三、 事件的對(duì)象
我們知道在觸發(fā)dom上的某個(gè)事件對(duì)象的時(shí)候,這個(gè)對(duì)象會(huì)包含著所有與事件有關(guān)的信息。包括事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。
-
dom的事件對(duì)象
兼容dom的瀏覽器會(huì)將一個(gè)event對(duì)象傳到事件處理程序中。例如:
btn.addEventListener("click",function(e){
console.log(e);
},false)
btn.onclick = function(e){
console.log(e);
};

上圖是我打印出來(lái)傳遞的event對(duì)象中所包含的值。我在這里呢?就簡(jiǎn)單的介紹幾個(gè)最基本的含義,剩下的其實(shí)大多數(shù)大家都能看懂。
| 屬性/方法 | 類型 | 讀寫 | 說(shuō)明 |
|---|---|---|---|
| bubbles | boolean | 只讀 | 表明事件是否冒泡 |
| cancelable | boolean | 只讀 | 表明是否可以取消事件的默認(rèn)行為 |
| currentTarget | Element | 只讀 | 其事件的處理程序正在處理程序的那個(gè)元素 |
| defaultPrevented | boolean | 只讀 | 為true表示已經(jīng)調(diào)用了preventDefault(dom3新增的) |
| detail | Integer | 只讀 | 與事件相關(guān)的詳細(xì)細(xì)節(jié) |
| eventPhase | Integer | 只讀 | 調(diào)用事件處理程序的階段:1表示捕獲,2處于目標(biāo),3處于冒泡 |
| preventDefault() | Function | 只讀 | 取消事件的 默認(rèn)行為,如果cancelable為true才能使用這個(gè)方法 |
| stopImmediatePropagation | Function | 只讀 | 取消事件的進(jìn)一步捕獲或冒泡,同時(shí)阻止任何事件處理程序被調(diào)用(dom3新增) |
| stopPropagation | Function | 只讀 | 取消事件的進(jìn)一步捕獲或者冒泡。如果cancelable為true的時(shí)候,才能使用這個(gè)方法 |
| target | Element | 只讀 | 事件的目標(biāo) |
| trusted | Boolean | 只讀 | 為true表示事件是瀏覽器生成的。為false表示事件又開發(fā)者通過(guò)js創(chuàng)建的(dom3新增的) |
| type | String | 只讀 | 被觸發(fā)事件的類型 |
| view | abstractview | 只讀 | 與事件相關(guān)聯(lián)的抽象視圖,等同于發(fā)生事件的window對(duì)象 |
上面表格中值得注意三點(diǎn)的就是:
this是始終等于currentTarget的值,而target則只包含事件的實(shí)際目標(biāo)
當(dāng)cancalable為true的時(shí)候,我們可以通過(guò)preventDefault函數(shù)來(lái)取消其默認(rèn)行為
eventPhase可以用來(lái)判斷事件的狀態(tài),捕獲狀態(tài)為1,目標(biāo)對(duì)象上為2,冒泡上為3 。值得注意的是,當(dāng)eventPhase為2的時(shí)候this,target,currentTarget是始終相等的。
-
ie的事件對(duì)象
和訪問(wèn)dom中的event不同,ie中訪問(wèn)event對(duì)象有幾種不同的方式。來(lái)看下面的例子
btn.onclick = function(){
var e = window.event;
console.log(e);
};
btn.attachEvent("onclick",function(e){
console.log(e)
})
同樣我也將ie事件的主要的幾個(gè)屬性也列在下面
| 屬性/方法 | 類型 | 讀寫 | 說(shuō)明 |
|---|---|---|---|
| cancelBubble | boolean | 讀/寫 | 默認(rèn)值為false,但將其設(shè)置為true就可以取消事件(和dom中的stopPropagation()方法作用相同) |
| returnValue | boolean | 讀寫 | 默認(rèn)值為true,但將其設(shè)置為false就可以取消事件默認(rèn)行為(跟dom中的preventDefault()方法的作用相同) |
| srcElement | element | 只讀 | 事件的目標(biāo)(雷同dom中的target) |
| type | String | 只讀 | 被觸發(fā)的事件的類型 |
上面也值得我們注意的幾點(diǎn)是:
- 在事件處理程序中(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
})
由前面表格所說(shuō)returnValue屬性相當(dāng)于DOM中的preventDefault()方法,能取消事件的默認(rèn)行為。在ie中,只要將returnValue設(shè)置成false,就可以組織默認(rèn)行為了。
-
跨瀏覽器的事件對(duì)象
前面僅僅封裝了事件的綁定和解綁,但是我們考慮到我們有的時(shí)候可能會(huì)用到事件的event對(duì)象,前面的介紹可以知道ie和dom中會(huì)有些許不同,此時(shí)我們就可以對(duì)事件進(jìn)行再次封裝。代碼如下:
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;
}
}
}
這里呢?主要總結(jié)了事件冒泡的一些跨瀏覽器的兼容性問(wèn)題,當(dāng)然值得注意的是IE 不支持事件捕獲的,所以這個(gè)方法不是不支持事件捕獲的。
說(shuō)在最后
十一呢?去過(guò)漂流,也去爬山,然后腿也瘸了。之后在家看了幾天的書籍。怎么說(shuō)呢?領(lǐng)悟到活著的好處,也領(lǐng)悟到活蹦亂跳的快樂(lè)了。我已經(jīng)決心不在做一個(gè)秀才,突然有個(gè)瞬間覺(jué)得這個(gè)城市這么大 我卻沒(méi)去過(guò)幾處的感覺(jué)。
嗯!關(guān)于接下來(lái)幾個(gè)月的規(guī)劃呢?我會(huì)每月寫一篇書評(píng),每周寫一篇Android,寫一篇前端、寫一篇小程序的文章來(lái)鞭策自己不斷的向前。敬請(qǐng)期待吧