【轉】JavaScript中的事件處理機制

什麼是事件?

事件(Event)是JavaScript應用跳動的心臟 ,也是把全部東西粘在一塊兒的膠水。當咱們與瀏覽器中 Web 頁面進行某些類型的交互時,事件就發生了。事件多是用戶在某些內容上的點擊、鼠標通過某個特定元素或按下鍵盤上的某些按鍵。事件還多是 Web 瀏覽器中發生的事情,好比說某個 Web 頁面加載完成,或者是用戶滾動窗口或改變窗口大小。javascript

經過使用 JavaScript ,你能夠監聽特定事件的發生,並規定讓某些事件發生以對這些事件作出響應。php

今天的事件

在漫長的演變史,咱們已經告別了內嵌式的事件處理方式(直接將事件處理器放在 HTML 元素以內來使用)。今天的事件,它已經是DOM的重要組成部分,遺憾的是, IE繼續保留它最先在IE4.0中實現的事件模型,之後的IE版本中也沒有作太大的改變,這也就是說IE仍是使用的是一種專有的事件模型(冒泡型),而其它的主流瀏覽器直到DOM 級別 3 規定定案後,才陸陸續續支持DOM標準的事件處理模型 — 捕獲型與冒泡型。css

歷史緣由是:W3C 規範 在DOM 級別 1中並無定義任何的事件,直到發佈於 2000 年 11 月 的DOM 級別 2 才定義了一小部分子集,DOM 級別 2中已經提供了提供了一種更詳細的更細緻的方式以控制 Web 頁面中的事件,最後,完整的事件是在2004年 DOM 級別 3的規定中才最終定案。由於IE4是1995推出的並已實現了本身的事件模型(冒泡型),當時根本就沒有DOM標準,不過在之後的DOM標準規範過程當中已經把IE的事件模型吸取到了其中。html

目前除IE瀏覽器外,其它主流的Firefox, Opera,
Safari都支持標準的DOM事件處理模型。IE仍然使用本身專有的事件模型,即冒泡型,它事件模型的一部份被DOM標準採用,這點對於開發者來講也是有好處的,只有使用
DOM標準,IE都共有的事件處理方式纔能有效的跨瀏覽器。java

DOM事件流

DOM(文檔對象模型)結構是一個樹型結構,當一個HTML元素產生一個事件時,該事件會在元素結點與根節點之間按特定的順序傳播,路徑所通過的節點都會收到該事件,這個傳播過程可稱爲DOM事件流。node

事件順序有兩種類型:事件捕捉事件冒泡瀏覽器

冒泡型事件(Event Bubbling)dom

這是IE瀏覽器對事件模型的實現,也是最容易理解的,至少筆者以爲比較符合實際的。冒泡,顧名思義,事件像個水中的氣泡同樣一直往上冒,直到頂端。從
DOM樹型結構上理解,就是事件由葉子節點沿祖先結點一直向上傳遞直到根節點;從瀏覽器界面視圖HTML元素排列層次上理解就是事件由具備從屬關係的最肯定的目標元素一直傳遞到最不肯定的目標元素.冒泡技術.冒泡型事件的基本思想,事件按照從特定的事件目標開始到最不肯定的事件目標.函數

捕獲型事件(Event Capturing)性能

Netscape 的實現,它與冒泡型恰好相反,由DOM樹最頂層元素一直到最精確的元素,這個事件模型對於開發者來講(至少是我..)有點費解,由於直觀上的理解應該如同冒泡型,事件傳遞應該由最肯定的元素,即事件產生元素開始。

 

event-buddle

DOM標準的事件模型

咱們已經對上面兩個不一樣的事件模型進行了解釋和對比。DOM標準同時支持兩種事件模型,即捕獲型事件冒泡型事件,可是,捕獲型事件先發生。兩種事件流都會觸發DOM中的全部對象,從document對象開始,也在document對象結束(大部分兼容標準的瀏覽器會繼續將事件是捕捉/冒泡延續到window對象)。

domevent

如圖:首先是捕獲式傳遞事件,接着是冒泡式傳遞,因此,若是一個處理函數既註冊了捕獲型事件的監聽,又註冊冒泡型事件監聽,那麼在DOM事件模型中它就會被調用兩次。

DOM標準的事件模型最獨特的性質是,文本節點也會觸發事件(在IE不會)。

capturing-bubbling

事件傳送

爲了更好的說明DOM標準中的事件流原理,咱們把它放在「事件傳送」小結裏來更具體的解釋。

顯然,若是爲一個超連接添加了click事件監聽器,那麼當該連接被點擊時該事件監聽器就會被執行。但若是把該事件監聽器指派給了包含該連接的p元素或者位於DOM樹頂端的document節點,那麼點擊該連接也一樣會觸發該事件監聽器。這是由於事件不只僅對觸發的目標元素產生影響,它們還會對沿着DOM結構的全部元素產生影響。這就是你們所熟悉的事件轉送

W3C事件模型中明確地指出了事件轉送的原理。事件傳送能夠分爲3個階段。

Standard-event-propagation

如圖:標準的事件轉送模式

(1).在事件捕捉(Capturing)階段,事件將沿着DOM樹向下轉送,目標節點的每個祖先節點,直至目標節點。例如,若用戶單擊了一個超連接,則該單擊事件將從document節點轉送到html元素,body元素以及包含該連接的p元素。

在此過程當中,瀏覽器都會檢測針對該事件的捕捉事件監聽器,而且運行這件事件監聽器。

(2). 在目標(target)階段,瀏覽器在查找到已經指定給目標事件的事件監聽器以後,就會運行 該事件監聽器。目標節點就是觸發事件的DOM節點。例如,若是用戶單擊一個超連接,那麼該連接就是目標節點(此時的目標節點其實是超連接內的文本節點)。

(3).在冒泡(Bubbling)階段,事件將沿着DOM樹向上轉送,再次逐個訪問目標元素的祖先節點到document節點。該過程當中的每一步。瀏覽器都將檢測那些不是捕捉事件監聽器的事件監聽器,並執行它們。

並不是全部的事件都會通過冒泡階段的

全部的事件都要通過捕捉階段和目標階段,可是有些事件會跳過冒泡階段。例如,讓元素得到輸入焦點的focus事件以及失去輸入焦點的blur事件就都不會冒泡。

 

事件句柄和事件接聽器

事件句柄

事件句柄(又稱事件處理函數,DOM稱之爲事件監聽函數),用於響應某個事件而調用的函數稱爲事件處理函數
。每個事件均對應一個事件句柄,在程序執行時,將相應的函數或語句指定給事件句柄,則在該事件發生時,瀏覽器便執行指定的函數或語句,從而實現網頁內容與用戶操做的交互。當瀏覽器檢測到某事件發生時,便查找該事件對應的事件句柄有沒有被賦值,若是有,則執行該事件句柄。

咱們認爲響應點擊事件的函數是onclick事件處理函數。之前,事件處理函數有兩種分配方式:在JavaScript中或者在HTML中

若是在JavaScript 中分配事件處理函數, 則須要首先得到要處理的對象的一引用,而後將函數賦值給對應的事件處理函數屬性,請看一個簡單的例子:

 

1 var link=document.getElementById("mylink");
2 link.onclick=function(){
3   alert("I was clicked !");
4 }; 

 

從咱們看到的例子中,咱們發現使用事件句柄很容易,
不過事件處理函數名稱必須是小寫的,還有就是隻有在
元素載入完成以後才能將事件句柄賦給元素,否則會有異常。

關於文檔載入技術,請看《window.onload加載的多種解決方案》 的文章。

若是在HTML中分配事件句柄的話,則直接經過HTML屬性來設置事件處理函數就好了,並在其中包含合適的腳本做爲特性值就能夠了,例如:

 <a href="/" onclick="JavaScript code here">......</a> 

這種JavaScript 代碼和經過HTML的style屬性直接將CSS屬性賦給元素相似。這樣會代碼看起來一團糟,也違背了將實現動態行爲的代碼與顯示文檔靜態內容的代碼相分離的原則。從1998年開始,這種寫法就過期了。

這種傳統的事件綁定技術,優缺點是顯然的:

*簡單方便,在HTML中直接書寫處理函數的代碼塊,在JS中給元素對應事件屬性賦值便可。

*IE與DOM標準都支持的一種方法,它在IE與DOM標準中都是在事件冒泡過程當中被調用的。

*能夠在處理函數塊內直接用this引用註冊事件的元素,this引用的是當前元素。

*要給元素註冊多個監聽器,就不能用這方法了。

事件接聽器

除了前面已經介紹的簡單事件句柄以外,如今大多數瀏覽器都內置了一些更高級的事件處理方式,即,事件監聽器,這種處理方式就不受一個元素只能綁定一個事件句柄的限制。

咱們已經知道了事件句柄與事件監聽器的最大不一樣之處是使用事件句柄時一次只能插接一個事件句柄,但對於事件監聽器,一次能夠插接多個。

IE下的事件監聽器:

IE提供的倒是一種自有的,徹底不一樣的甚至存在BUG的事件監聽器,所以若是要讓腳本在本瀏覽器中正常運行的話,就必須使用IE所支持的事件監聽器。另外,Safari 瀏覽器中的事件監聽器有時也存在一點不一樣。

在IE中,每一個元素和window對象都有兩個方法:attachEvent方法和detachEvent方法。 

1 element.attachEvent("onevent",eventListener);

此方法的意思是在IE中要想給一個元素的事件附加事件處理函數,必須調用attachEvent方法才能建立一個事件監聽器。attachEvent方法容許外界註冊該元素多個事件監聽器。

attachEvent接受兩個參數。第一個參數是事件類型名,第二個參數eventListener是回調處理函數。這裏得說明一下,有個常常會出錯的地方,IE下
利用attachEvent註冊的處理函數調用時this指向再也不是先前註冊事件的元素,這時的this爲window對象。還有一點是此方法的事件類型名稱必須加上一個」on」的前綴(如onclick)。 

1 element.attachEvent("onevent",eventListener);

要想移除先前元素註冊的事件監聽器,可使用detachEvent方法進行刪除,參數相同。

DOM標準下的事件監聽器:

在支持W3C標準事件監聽器的瀏覽器中,對每一個支持事件的對象均可以使用addEventListener方法。該方法既支持註冊冒泡型事件處理,又支持捕獲型事件處理。因此與IE瀏覽器中註冊元素事件監聽器方式有所不一樣的。

1 //標準語法 
2 element.addEventListener('event', eventListener, useCapture);
3 //默認
4 element.addEventListener('event', eventListener, false);

addEventListener方法接受三個參數。第一個參數是事件類型名,值得注意的是,這裏事件類型名稱與IE的不一樣,事件類型名是沒’on’開頭的;第二個參數eventListener是回調處理函數(即監聽器函數);第三個參數註明該處理回調函數是在事件傳遞過程當中的捕獲階段被調用仍是冒泡階段被調用 ,一般此參數一般會設置爲false(爲false時是冒泡),那麼,若是將其值設置爲true,那就建立一個捕捉事件監聽器。

移除已註冊的事件監聽器調用element的removeEventListener方法便可,參數相同。

1 //標準語法 
2 element.removeEventListener('event', eventListener, useCapture);
3 //默認
4 element.removeEventListener('event', eventListener, false);

經過addEventListener方法添加的事件處理函數,必須使用removeEventListener方法才能刪除,並且要求參數與添加事件處理函數時addEventListener方法的參數徹底一致(包括useCapture參數),不然將不能成功刪除事件處理函數。

跨瀏覽器的註冊與移除元素事件監聽器方案

咱們如今已經知道,對於支持addEventListener方法的瀏覽器,只要須要事件監聽器腳本就都須要調用addEventListener方法;而對於不支持該方法的IE瀏覽器,使用事件監聽器時則須要調用attachEvent方法。要確保瀏覽器使用正確的方法其實並不困難,只須要經過一個if-else語句來檢測當前瀏覽器中是否存在addEventListener方法或attachEvent方法便可。

這樣的方式就能夠實現一個跨瀏覽器的註冊與移除元素事件監聽器方案:

 1 var EventUtil = {
 2   //註冊
 3   addHandler: function(element, type, handler){
 4     if (element.addEventListener){
 5       element.addEventListener(type, handler, false);
 6     } else if (element.attachEvent){
 7       element.attachEvent("on" + type, handler);
 8     } else {
 9       element["on" + type] = handler;
10     }
11   },
12   //移除註冊
13   removeHandler: function(element, type, handler){
14     if (element.removeEventListener){
15             element.removeEventListener(type, handler, false);
16     } else if (element.detachEvent){
17             element.detachEvent("on" + type, handler);
18     } else {
19             element["on" + type] = null;
20     }
21   }             
22  }; 

 

事件對象引用

爲了更好的處理事件,你能夠根據所發生的事件的特定屬性來採起不一樣的操做。

如事件模型同樣,IE 和其餘瀏覽器處理方法不一樣:IE 使用一個叫作 event 的全局事件對象來處理對象(它能夠在全局變量window.event中找到),而其它全部瀏覽器採用的 W3C 推薦的方式,則使用獨立的包含事件對象的參數傳遞。

跨瀏覽器實現這樣的功能時,最多見的問題就是獲取事件自己的引用及獲取該事件的目標元素的引用。

下面這段代碼就爲你解決了這個問題:

1 var EventUtil ={
2   getEvent: function(event){
3     return event ? event : window.event;
4   },
5   getTarget: function(event){
6     return event.target || event.srcElement;
7   }
8 };

 

中止事件冒泡和阻止事件的默認行爲

「中止事件冒泡「和」阻止瀏覽器的默認行爲「,這兩個概念很是重要,它們對複雜的應用程序處理很是有用。

1.中止事件冒泡

中止事件冒泡是指,中止冒泡型事件的進一步傳遞(取消事件傳遞,不僅是中止IE和DOM標準共有的冒泡型事件,咱們還能夠中止支持DOM標準瀏覽器的捕捉型事件,用topPropagation()方法)。例如上圖中的冒泡型事件傳遞中,在body處理中止事件傳遞後,位於上層的document的事件監聽器就再也不收到通知,再也不被處理。

2.阻止事件的默認行爲

中止事件的默認行爲是指,一般瀏覽器在事件傳遞並處理完後會執行與該事件關聯的默認動做(若是存在這樣的動做)。例如,若是表單中input type 屬性是 「submit」,點擊後在事件傳播完瀏覽器就自動提交表單。又例如,input 元素的 keydown 事件發生並處理後,瀏覽器默認會將用戶鍵入的字符自動追加到 input 元素的值中。

中止事件冒泡的處理方法

在IE下,經過設置event對象的cancelBubble爲true便可。

1 function someHandle() {
2   window.event.cancelBubble = true;
3 }

DOM標準經過調用event對象的stopPropagation()方法便可。

1 function someHandle(event) {
2   event.stopPropagation();
3 }

因些,跨瀏覽器的中止事件傳遞的方法是:

1 function someHandle(event) {
2   event = event || window.event;
3   if(event.stopPropagation){
4     event.stopPropagation();
5   }else {
6     event.cancelBubble = true;
7   }
8 }

 

阻止事件的默認行爲的處理方法

就像事件模型和事件對象差別同樣,在IE和其它全部瀏覽器中阻止事件的默認行爲的方法也不一樣。

在IE下,經過設置event對象的returnValue爲false便可。

1 function someHandle() {
2   window.event.returnValue = false;
3 }

DOM標準經過調用event對象的preventDefault()方法便可。

1 function someHandle(event) {
2   event.preventDefault();
3 }

因些,跨瀏覽器的取消事件傳遞後的默認處理方法是:

1 function someHandle(event) {
2   event = event || window.event;
3   if(event.preventDefault){
4     event.preventDefault();
5   }else{
6     event.returnValue = false;
7   }
8 }

完整的事件處理兼容性函數 

 1 var EventUtil = {
 2   addHandler: function(element, type, handler){
 3     if (element.addEventListener){
 4       element.addEventListener(type, handler, false);
 5     } else if (element.attachEvent){
 6       element.attachEvent("on" + type, handler);
 7     } else {
 8       element["on" + type] = handler;
 9     }
10   },
11   removeHandler: function(element, type, handler){
12     if (element.removeEventListener){
13       element.removeEventListener(type, handler, false);
14     } else if (element.detachEvent){
15       element.detachEvent("on" + type, handler);
16     } else {
17       element["on" + type] = null;
18     }
19   },
20   getEvent: function(event){
21     return event ? event : window.event;
22   },
23   getTarget: function(event){
24     return event.target || event.srcElement;
25   },
26   preventDefault: function(event){
27     if (event.preventDefault){
28       event.preventDefault();
29     } else {
30       event.returnValue = false;
31     }
32   },
33   stopPropagation: function(event){
34     if (event.stopPropagation){
35       event.stopPropagation();
36     } else {
37       event.cancelBubble = true;
38     }
39 };

 

捕獲型事件模型與冒泡型事件模型的應用場合

標準事件模型爲咱們提供了兩種方案,可能不少朋友分不清這兩種不一樣模型有啥好處,爲何不僅採起一種模型。
這裏拋開IE瀏覽器討論(IE只有一種,無法選擇)什麼狀況下適合哪一種事件模型。

1. 捕獲型應用場合

捕獲型事件傳遞由最不精確的祖先元素一直到最精確的事件源元素,傳遞方式與操做系統中的全局快捷鍵與應用程序快捷鍵類似。當一個系統組合鍵發生時,若是注
冊了系統全局快捷鍵監聽器,該事件就先被操做系統層捕獲,全局監聽器就先於應用程序快捷鍵監聽器獲得通知,也就是全局的先得到控制權,它有權阻止事件的進
一步傳遞。因此捕獲型事件模型適用於做全局範圍內的監聽,這裏的全局是相對的全局,相對於某個頂層結點與該結點全部子孫結點造成的集合範圍。

例如你想做全局的點擊事件監聽,相對於document結點與document下全部的子結點,在某個條件下要求全部的子結點點擊無效,這種狀況下冒泡模型就解決不了了,而捕獲型卻很是適合,能夠在最頂層結點添加捕獲型事件監聽器,僞碼以下:

1 function globalClickListener(event) {
2   if(canEventPass == false) {
3     //取消事件進一步向子結點傳遞和冒泡傳遞
4     event.stopPropagation();
5     //取消瀏覽器事件後的默認執行
6     event.preventDefault();
7   }
8 }

這樣一來,當canEventPass條件爲假時,document下全部的子結點click註冊事件都不會被瀏覽器處理。

2. 冒泡型的應用場合

能夠說咱們平時用的都是冒泡事件模型,由於IE只支持這模型。這裏仍是說說,在恰當利用該模型能夠提升腳本性能。在元素一些頻繁觸發的事件中,如
onmousemove,
onmouseover,onmouseout,若是明確事件處理後不必進一步傳遞,那麼就能夠大膽的取消它。此外,對於子結點事件監聽器的處理會對父
層監聽器處理形成負面影響的,也應該在子結點監聽器中禁止事件進一步向上傳遞以消除影響。

 

綜合案例分析

最後結合下面HTML代碼做分析:

1 <body onclick="alert('current is body');">
2   <div id="div0" onclick="alert('current is '+this.id)">
3     <div id="div1" onclick="alert('current is '+this.id)">
4       <div id="div2" onclick="alert('current is '+this.id)">
5         <div id="event_source" onclick="alert('current is '+this.id)" style="height:200px;width:200px;background-color:red;"></div>
6       </div>
7     </div>
8   </div>
9 </body>

 

HTML運行後點擊紅色區域,這是最裏層的DIV,根據上面說明,不管是DOM標準仍是IE,直接寫在html裏的監聽處理函數是事件冒泡傳遞時調用的,由最裏層一直往上傳遞,因此會前後出現
current is event_source
current is div2
current is div1
current is div0
current is body

添加如下片斷:

1 var div2 = document.getElementById('div2');
2 EventUtil.addHandler(div2, 'click', function(event){
3   event = EventUtil.getEvent(event);
4   EventUtil.stopPropagation(event);
5 }, false);

current is event_source

current is div2

當點擊紅色區域後,根據上面說明,在泡冒泡處理期間,事件傳遞到div2後被中止傳遞了,因此div2上層的元素收不到通知,因此會前後出現:

在支持DOM標準的瀏覽器中,添加如下代碼:

1 document.body.addEventListener('click', function(event){
2    event.stopPropagation();
3 }, true);

 

以上代碼中的監聽函數因爲是捕獲型傳遞時被調用的,因此點擊紅色區域後,雖然事件源是ID爲event_source的元素,但捕獲型選傳遞,從最頂層開始,body結點監聽函數先被調用,而且取消了事件進一步向下傳遞,因此只會出現 current is body .

 

原文:http://www.cnblogs.com/binyong/articles/1750263.html

其餘文章:

http://www.jb51.net/article/42492.htm

http://www.jb51.net/article/53438.htm

http://www.cnblogs.com/aji88/archive/2012/07/20/2600492.html

DOM簡介:

http://baike.baidu.com/subview/14806/8904138.htm

http://www.w3school.com.cn/htmldom/dom_intro.asp

DOM事件流:http://baike.baidu.com/view/1148456.htm

事件類型:http://www.jb51.net/article/60124.htm

事件對象:http://www.jb51.net/article/60122.htm

相關文章
相關標籤/搜索