javascript 事件處理
【寫在前面】最近一直在看js的基礎,畢竟jquery雖然好用,總歸是用著別人寫的,只會用api不如搞清楚實現的原理。等把js基礎鞏固好了一定要去讀jquery的源碼。
事件流
事件流描述的是從頁面中接收事件的順序。js中有兩種事件流,冒泡流和捕獲流。兩種流的名稱都很形象,在由節點構成的一顆html樹上面,冒泡流指事件開始時候由最具體的元素最先接收,然後逐級傳播到最不具體的節點,比如在如下文檔中:
當單擊div時,事件首先在div上響應,然後傳到body元素上,然後傳到html元素上,最後傳到document對象。
捕獲流正好相反,最先響應的是document對象,然後沿dom樹依次向下,到html,到body,最後到實際目標,也就是div。
老版瀏覽器並不支援捕獲流,冒泡流也非常符合我們對事件傳播的理解,因此我們更多使用的是冒泡流。
事件處理的幾種方法
在js的事件處理中定義了很多種事件,事件名稱是指具體的某種動作,比如click、load、mouseover等都是事件的名稱。而響應某個事件的函數叫事件處理常式,事件處理常式的名字是以on開頭的,因此click事件的事件處理常式就是onclick,load事件的事件處理常式就是onload。
標籤內綁定
通過設定標籤的屬性可以為節點十分方便地綁定事件,如:
也可以調用其他地方定義的指令碼:
<script> function showMessage(){ alert(按鈕按下); }</script>...
使用直接綁定有兩個特別的地方,其一是會建立一個封裝元素屬性的函數,這個函數中有一個局部變數event,也就是事件對象:
事件產生時,event對象自動傳遞到showMessage函數中,通過這個對象我們可以訪問到事件對象的很多屬性。另外在事件處理常式內部,this指代事件的目標元素,比如
另外噹噹前元素是一個表單元素時,則範圍中還會包含表單元素的入口,也就是說在事件處理常式中可以訪問其他表單元素,例如
這樣可以直接輸出username中的值。
然而通過這種方式添加的事件處理雖然直觀方便,但有很多壞處:
(1)因為頁面載入順序是從上往下,如果綁定的指令碼寫在html下面,可能使用者點擊元素的時候指令碼還未執行,造成使用者點半天沒反應,如:
......//很多內容...<script> function showMessage(){ //... }</script>
(2)html和js代碼緊密結合,比如showMessage這個方法,如果介面內很多元素都通過這種方式綁定了這個方法,那麼如果某一刻想給這個函數換一個名字時,你需要同時改動很多處地方,相當麻煩。
DOM0級處理常式
第二種方式是在js中為元素繫結事件,通過在js中擷取元素,然後給其指定事件:
var btn = document.getElementById(button);btn.onclick = function(){ alert(this.id);}
通過DOM0級方法指定的程式被認為是元素的方法,所以上述代碼執行後,按下元素彈出的將是按鈕的id,可以通過this擷取該元素所有的屬性和方法。
需要刪除通過DOM0級方法指定的事件處理常式,只需將該屬性值設為null即可:
btn.onclick = null;
需要注意的是通過這種方式不能為元素添加重複事件,多次為onclick賦值只有最後一個是有效,比如
btn.onclick = function(){ alert(first);}btn.onclick = function(){ alert(second);}
最後單擊按鈕的時候只會輸出second。
DOM2級處理常式
IE9、Firefox、chrome、safari和opera支援DOM2級事件處理常式
“DOM2級事件”定義了兩個方法,用於指定和刪除事件處理常式的操作:addEventListener()和removeEventListener()。所有DOM節點都有這個方法,並接收3個參數:事件名、事件處理函數和一個布爾值。這個布爾值代表了是使用捕獲還是冒泡,true代表在捕獲階段處理,false代表在冒泡階段處理,不填預設是false。
var btn = document.getElementById(button);btn.addEventListener(click, function(){ //...}, false);
和DOM0級事件處理不同,通過這種方法綁定的事件處理可以多次添加,比如
var btn = document.getElementById(button);btn.addEventListener(click, function(){ alert(first);}, false);btn.addEventListener(click, function(){ alert(this.id);}, false);
而且在函數內this同樣指代目標元素,點擊按鈕後會先輸出first,再輸出該btn的id。
通過addEventListener添加的事件只能通過removeEventListener移除,而且參數必須一樣(三個參數一模一樣),這代表使用匿名函數綁定的事件是無法移除的,最好將事件處理函數單獨定義。
btn.addEventListener(click, function(){ alert(first);}, false);btn.removeEventListener(click, function(){ alert(first);}, false); //沒有效果btn.addEventListener(click, showMessage, false);btn.removeEventListener(click, showMessage, false); //移除成功
IE事件處理常式
IE中單獨實現了與DOM2級事件處理中類似的兩個方法:attachEvent和detachEvent()。這兩個方法接收兩個參數:事件處理常式名稱和事件處理函數。因為IE8及更早版本只支援事件冒泡,所以通過這種方式添加的事件處理常式都會被添加在事件冒泡階段。
var btn = document.getElementById(button);btn.attachEvent(onclick, function(){ //注意是onclick alert(first);});
IE事件處理同之前的其他方式有兩個區別,第一,IE的事件處理常式會在全域環境下運行。
這代表在其中使用this會等於window。
var btn = document.getElementById(button);btn.attachEvent(onclick, function(){ alert(this === window); //true});
第二,多次添加的事件處理函數按添加的相反順序執行,比如
var btn = document.getElementById(button);btn.attachEvent(onclick, function(){ alert(first);});btn.attachEvent(onclick, function(){ alert(second);});
當按下按鈕時,將先輸出second,然後才輸出first。
要移除事件的方式同DOM2級相似,調用detachEvent然後傳如相同參數即可。
編寫跨瀏覽器的事件處理常式
上面提到IE和其他瀏覽器在處理事件上還是存在一些差異的,可以編寫一個EventUtil對象,來將添加事件和移除事件封裝起來。
實現方式是先判斷是否支援DOM2級處理,如果不支援採用IE方法,如果都不支援最後採用DOM0級方法。
var EventUtil = { //添加事件 addHandler: function(element, type, handler){ if(element.addEventListener){ //支援DOM2級 element.addEventListener(type, element, handler); }else if(element.attachEvent){ //IE事件處理,注意on首碼 element.attachEvent(on + type, handler); }else{ //DOM0級 element[on + type] = handler; } }, //移除事件 removeHandler: function(){ if(element.removeEventListener){ //支援DOM2級 element.removeEventListener(type, element, handler); }else if(element.detachEvent){ //IE事件處理,注意on首碼 element.detachEvent(on + type, handler); }else{ //DOM0級 element[on + type] = null; } }}