首頁  >  文章  >  web前端  >  JavaScript事件處理機制解析

JavaScript事件處理機制解析

小云云
小云云原創
2018-03-05 15:43:212382瀏覽

事件(Event)是JavaScript應用跳動的心 ,也是把所有東西黏在一起的膠水。當我們與瀏覽器中 Web 頁面進行某些類型的互動時,事件就發生了。事件可能是使用者在某些內容上的點擊、滑鼠經過某個特定元素或按下鍵盤上的某些按鍵。事件也可能是 Web 瀏覽器中發生的事情,比如說某個 Web 頁面載入完成,或是使用者捲動視窗或改變視窗大小。

透過使用 JavaScript ,你可以監聽特定事件的發生,並規定讓某些事件發生以對這些事件做出回應。

今天的事件

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

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

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

DOM事件流

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

事件順序有兩種:事件捕捉和事件冒泡。

冒泡型事件(Event Bubbling)

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

捕獲型事件(Event Capturing)

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

 

JavaScript事件處理機制解析

DOM標準的事件模型

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

JavaScript事件處理機制解析

如圖:首先是捕獲式傳遞事件,接著是冒泡式傳遞,所以,如果一個處理函數既註冊了捕獲型事件的監聽,又註冊冒泡型事件監聽,那麼在DOM事件模型中它就會被呼叫兩次。

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

JavaScript事件處理機制解析

事件傳送

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

顯然,如果為一個超連結新增了click事件監聽器,那麼當該連結被點擊時該事件監聽器就會被執行。但如果把該事件監聽器指派給了包含該連結的p元素或位於DOM樹頂端的document節點,那麼點擊該連結也同樣會觸發該事件監聽器。這是因為事件不僅對觸發的目標元素產生影響,它們還會對沿著DOM結構的所有元素產生影響。這就是大家所熟悉的事件轉送。

W3C事件模型中明確地指出了事件轉送的原則。事件傳送可以分為3個階段。

JavaScript事件處理機制解析

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

(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>......</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(&#39;event&#39;, eventListener, useCapture);3 //默认4 element.addEventListener(&#39;event&#39;, eventListener, false);

addEventListener方法接受三个参数。第一个参数是事件类型名,值得注意的是,这里事件类型名称与IE的不同,事件类型名是没’on’开头的;第二个参数eventListener是回调处理函数(即监听器函数);第三个参数注明该处理回调函数是在事件传递过程中的捕获阶段被调用还是冒泡阶段被调用 ,通常此参数通常会设置为false(为false时是冒泡),那么,如果将其值设置为true,那就创建一个捕捉事件监听器。

移除已注册的事件监听器调用element的removeEventListener方法即可,参数相同。

1 //标准语法 2 element.removeEventListener(&#39;event&#39;, eventListener, useCapture);3 //默认4 element.removeEventListener(&#39;event&#39;, 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  }; 

JavaScript事件處理機制解析

事件物件參考

為了更好的處理事件,你可以根據所發生的事件的特定屬性來採取不同的操作。

如事件模型一樣,IE 和其他瀏覽器處理方法不同:IE 使用一個稱為event 的全域事件物件來處理物件(它可以在全域變數window.event中找到),而其它所有瀏覽器採用的W3C 建議的方式,則使用獨立的包含事件物件的參數傳遞。

跨瀏覽器實作這樣的功能時,最常見的問題就是取得事件本身的參考及取得該事件的目標元素的參考。

下面這段程式碼就為你解決了這個問題:

1 var EventUtil ={
2   getEvent: function(event){
3     return event? 。 #停止事件冒泡和阻止事件的預設行為

“停止事件冒泡“和”阻止瀏覽器的預設行為“,這兩個概念非常重要,它們對複雜的應用程式處理非常有用。

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){
  #o 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){
1 #5   }else{
6     event.returnValue = false;

7   }

8 }

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






#####################11127

 1 var EventUtil = {
 2   addHandler: 函數(element, type, handler){
 3     if se);
 5     } else if (element.attachEvent){
 6       element.attachEvent("on" + type, ement["on" + type] = handler ;
 9     }
10   },
11   removeHandler: 函數(元素, 類型, 處理程序){
#12   EventEvent>15(類型, 處理程序, false);
14     } else if (element.detachEvent){
15       element.detachEvent("     element["on " +類型] = null;
18     }
19   },
20   getEvent: 函數(事件){
#21    ? getEvent: 函數(事件){
#21    ?  事件:window.event;
22   },
23   getTarget:函數(事件){
24     回傳 event.target || event.srcElement;
25   },
26   preventDefault: 函數(事件){
27     if (event.prevent {
30       event.returnValue = false;
31     }
32   },
33   stopPropagation:100事件(事件){
33   stopPropagation:100事件(事件){##341     event.stopPropagation( );
36     } else {
37       event.cancelBubble = true;
38     }
39 };
38     }
39 };
38     }
39 };
#1型捕捉模型的應用模式
##標準事件模型為我們提供了兩種方案,可能很多朋友分不清這兩種不同的模型有啥好處,為什麼不只採用一種模型。 IE只有一種,無法選擇)什麼情況適合哪一種事件模型。 捕獲型應用場合

捕獲型事件傳遞由最精確的祖先元素一直到最精確的事件來源元素,傳遞方式與網路中的全域快捷鍵與應用程式快捷鍵。 ,如果註冊了系統全域快捷鍵監聽器,該事件就先被操作系統層組合捕獲,全域快捷鍵就先在應用程式快捷鍵監聽器得到通知,首先全局的先獲得控制權,就可以了可以阻止事件的進一步傳遞。 所以捕捉事件模型適用於做全域範圍內的監聽,這裡的全域是相對的全域型,相對於某個結點與該結點所有子孫結點形成的集合範圍。 ##例如你想做全域的點擊事件監聽,相對於文檔結點與文檔下所有的子結點,在某個條件下要求所有的子結點點擊無效,這種情況下冒泡模型就解決不了了,而捕獲型非常適合,可以在最困難的結點添加捕獲型事件監聽器,偽碼如下:

1 function globalClickListener(event) {


#2 if(canEventPass == false) {

3     //取消事件進一步向子結點提交和冒泡提交

4     event.stopPropagation();#       event.stopPropagation();#    

6     event.preventDefault();

7   }

8 }


這樣爆發,當canEventPass條件為假時,document下面所有的子結點點選註冊事件都不會被瀏覽器處理。 冒泡型的應用場合

可以說我們平常用的都是冒泡事件模型,因為IE只支援這個模型。這裡還是說說,在恰當利用該模型可以提高腳本效能。在元素一些頻繁觸發的事件中,如onmousemove,onmouseover,onmouseout,如果明確事件處理後沒必要進一步傳遞,那麼就可以大膽的取消它。此外,對於子結點事件監聽器的處理會對父
層監聽器處理造成負面影響的,也應該在子結點監聽器中禁止事件進一步向上傳遞以消除影響。

綜合案例分析

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

 1 


 2   


 3     


 4       


 5  alert('current is '+this.id)" style="height:200px;width:200px;background-color:red;">


 6      
 8   
 9 
10 
#HTML運行後點選紅色區域,這是最裡層的p,根據上面說明,無論是DOM標準或IE,直接寫在html裡的監聽處理函數是事件冒泡傳遞時調用的,由最裡層一直往上傳遞,所以會先後出現

current is event_source
#current is p2
current is p1
current is p0
current is body

新增下列片段:

1 var p2 = document.getElementById('p2');

2 EventUtil.addHandler(p2, 'click', function(event){
3   event = EventUtil.getEvent(event);
4   EventUtil.stopPropagation(event));
4   EventUtil.stopPropagation(event);##5);#f ;

current is event_sourcecurrent is p2

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

在支援DOM標準的瀏覽器中,加入以下程式碼:

1 document.body.addEventListener('click', function(event) {

2   event.stopPropagation();
3 }, true);

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

相關推薦:


node.js中的事件處理機制詳解

#javascript 瀏覽器相容性事件處理機制

javascript打造跨瀏覽器事件處理機制[Blue-Dream出品]_javascript技巧

以上是JavaScript事件處理機制解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn