來源 《Javascript高級程序設計》 筆記
JavaScript與HTML之間的交互是通過事件實現的。
事件:文檔或瀏覽器窗口中發生的一些特定的交互瞬間。
就是文檔或瀏覽器窗口中發生的一些 特定的交互瞬間。可以使用偵聽器(或處理程序)來預訂事件,以便事件發生時執行相應的代 碼。這種在傳統軟件工程中被稱爲觀察員模式的模型,支持頁面的行爲(JavaScript代碼)與頁 面的外觀(HTML和 CSS代碼)之間的鬆散耦合。
瀏覽器的事件系統相對比較複雜。儘管所有主要瀏覽器已經實現了“DOM2 級事件”,但這個規範 本身並沒有涵蓋所有事件類型。瀏覽器對象模型(BOM)也支持一些事件,這些事件與文檔對象模型 (DOM)事件之間的關係並不十分清晰,因爲 BOM事件長期沒有規範可以遵循(HTML5後來給出了詳 細的說明)。隨着 DOM3級的出現,增強後的 DOM事件 API變得更加繁瑣。使用事件有時相對簡單, 有時則非常複雜,難易程度會因你的需求而不同。不過,有關事件的一些核心概念是一定要理解的。
事件是將 JavaScript與網頁聯繫在一起的主要方式。“DOM3級事件”規範和HTML5定義了常見的 大多數事件。即使有規範定義了基本事件,但很多瀏覽器仍然在規範之外實現了自己的專有事件,從而 爲開發人員提供更多掌握用戶交互的手段。有些專有事件與特定設備關聯,例如移動 Safari 中的 orientationchange 事件就是特定關聯 iOS設備的。
在使用事件時,需要考慮如下一些內存與性能方面的問題。
- 有必要限制一個頁面中事件處理程序的數量,數量太多會導致佔用大量內存,而且也會讓用戶 感覺頁面反應不夠靈敏。
- 建立在事件冒泡機制之上的事件委託技術,可以有效地減少事件處理程序的數量。
- 建議在瀏覽器卸載頁面之前移除頁面中的所有事件處理程序。
可以使用 JavaScript在瀏覽器中模擬事件。“DOM2級事件”和“DOM3級事件”規範規定了模擬事 件的方法,爲模擬各種有定義的事件提供了方便。此外,通過組合使用一些技術,還可以在某種程度上 模擬鍵盤事件。IE8及之前版本同樣支持事件模擬,只不過模擬的過程有些差異。 事件是 JavaScript中重要的主題之一,深入理解事件的工作機制以及它們對性能的影響至關重要。
事件學習的過程如下
目錄
事件流
事件流:描述從頁面接收事件的順序。
事件流描述的是從頁面中接收事件的順序。但有意思的是,IE 和 Netscape 開發團隊居然提出了差 不多是完全相反的事件流的概念。IE的事件流是事件冒泡流,而 Netscape Communicator的事件流是事 件捕獲流。
IE事件流(事件冒泡)
IE的事件流叫做事件冒泡(event bubbling),即事件開始時由具體的元素(文檔中嵌套層次深 的那個節點)接收,然後逐級向上傳播到較爲不具體的節點(文檔)。以下面的 HTML頁面爲例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id = "myDiv">Click Me</div>
</body>
</html>
點擊click事件
傳播順序:
(1)< div>
(2)< body>
(3)< html>
(4)< document>
IE5.5 及更早版本中的事件冒 泡會跳過<html>元素(從<body>直接跳到 document)。IE9、Firefox、Chrome和 Safari則將事件一直 冒泡到 window 對象。
所有現代瀏覽器都支持事件冒泡,在具體實現上還是有一些差別。
其他瀏覽器(事件捕獲)
Netscape Communicator團隊提出的另一種事件流叫做事件捕獲(event capturing)。事件捕獲的思想 是不太具體的節點應該更早接收到事件,而具體的節點應該後接收到事件。事件捕獲的用意在於在 事件到達預定目標之前捕獲它。如果仍以前面的 HTML頁面作爲演示事件捕獲的例子,那麼單擊<div> 元素就會以下列順序觸發 click 事件。
(1) document
(2) <html>
(3) <body>
(4) <div>
在事件捕獲過程中,document 對象首先接收到 click 事件,然後事件沿DOM樹依次向下,一直 傳播到事件的實際目標,即<div>元素
雖然事件捕獲是 Netscape Communicator 唯一支持的事件流模型,但 IE9、Safari、Chrome、Opera 和 Firefox目前也都支持這種事件流模型。儘管“DOM2級事件”規範要求事件應該從 document 對象 開始傳播,但這些瀏覽器都是從 window 對象開始捕獲事件的。 由於老版本的瀏覽器不支持,因此很少有人使用事件捕獲。我們也建議讀者放心地使用事件冒泡, 在有特殊需要時再使用事件捕獲。
DOM事件流
“DOM2級事件”規定的事件流包括三個階段:
- 事件捕獲階段
- 處於目標階段
- 事件冒泡階段
首 先發生的是事件捕獲,爲截獲事件提供了機會。然後是實際的目標接收到事件。後一個階段是冒泡階 段,可以在這個階段對事件做出響應。以前面簡單的 HTML頁面爲例,單擊<div>元素會按照圖13-3所 示順序觸發事件。
多數支持 DOM事件流的瀏覽器都實現了一種特定的行爲;即使“DOM2級事件”規範明確要求捕 獲階段不會涉及事件目標,但 IE9、Safari、Chrome、Firefox和 Opera 9.5及更高版本都會在捕獲階段觸 發事件對象上的事件。結果,就是有兩個機會在目標對象上面操作事件。
IE9、Opera、Firefox、Chrome和 Safari都支持 DOM事件流;IE8及更早版本不 支持 DOM事件流。
事件處理程序
定義:事件就是用戶或瀏覽器自身執行的某種動作。諸如 click、load 和 mouseover,都是事件的名字。 而響應某個事件的函數就叫做事件處理程序(或事件偵聽器)。
事件處理程序的名字以"on"開頭,因此 click 事件的事件處理程序就是 onclick,load 事件的事件處理程序就是 onload。爲事件指定處理 程序的方式有好幾種。
- HTML事件處理程序
- DOM0級事件處理程序
- DOM2級事件處理程序
- IE事件處理程序
- 跨瀏覽器事件處理程序
HTML事件處理程序
某個元素支持的每種事件,都可以使用一個與相應事件處理程序同名的 HTML 特性來指定。這個 特性的值應該是能夠執行的 JavaScript代碼。例如,要在按鈕被單擊時執行一些 JavaScript,可以像下面 這樣編寫代碼:
<input type="button" value="Click Me" οnclick="alert('Clicked')" />
這個操作是通過指定 onclick 特性並將一些 JavaScript 代碼作爲它的值來定義的。由於這個值是 JavaScript,因此不能在其中使用未經轉義的 HTML語法字符, 例如和號(&)、雙引號("")、小於號(<)或大於號(>)。爲了避免使用 HTML 實體,這裏使用了單 引號。如果想要使用雙引號,那麼就要將代碼改寫成如下所示:
<input type="button" value="Click Me" οnclick="alert("Clicked")" />
調用腳本函數
<script type="text/javascript">
function showMessage(){
alert("Hello world!");
}
</script>
<input type="button" value="Click Me" οnclick="showMessage()" />
事件處理程序中的代碼在執行時,有權訪問全局作用 域中的任何代碼。
<form method="post">
<input type="text" name="username" value="">
<input type="button" value="Echo Username" οnclick="alert(username.value)">
</form>
在這個例子中,單擊按鈕會顯示文本框中的文本。值得注意的是,這裏直接引用了 username 元素。
不過,在 HTML 中指定事件處理程序有兩個缺點。首先,存在一個時差問題。因爲用戶可能會在 HTML元素一出現在頁面上就觸發相應的事件,但當時的事件處理程序有可能尚不具備執行條件。假設 showMessage()函數是在按鈕下方、頁面的底部定義的。如果用戶在頁面解 析 showMessage()函數之前就單擊了按鈕,就會引發錯誤。爲此,很多 HTML事件處理程序都會被封 裝在一個 try-catch 塊中,以便錯誤不會浮出水面,如下面的例子所示:
<input type="button" value="Click Me" οnclick="try{showMessage();}catch(ex){}">
缺點
1.如果在 showMessage()函數有定義之前單擊了按鈕,用戶將不會看到 JavaScript錯誤,因爲 在瀏覽器有機會處理錯誤之前,錯誤就被捕獲了。
2.這樣擴展事件處理程序的作用域鏈在不同瀏覽器中會導致不同結果。不同 JavaScript 引擎遵循的標識符解析規則略有差異,很可能會在訪問非限定對象成員時出錯。
3.是 HTML與 JavaScript代碼緊密耦合。如果要更換事 件處理程序,就要改動兩個地方:HTML代碼和 JavaScript代碼。而這正是許多開發人員摒棄 HTML事 件處理程序,轉而使用 JavaScript指定事件處理程序的原因所在。
DOM0級事件處理程序
通過 JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。這 種爲事件處理程序賦值的方法是在第四代 Web 瀏覽器中出現的,而且至今仍然爲所有現代瀏覽器所支 持。原因一是簡單,二是具有跨瀏覽器的優勢。要使用 JavaScript 指定事件處理程序,首先必須取得一 個要操作的對象的引用。
每個元素(包括 window 和 document)都有自己的事件處理程序屬性,這些屬性通常全部小寫, 例如 onclick。將這種屬性的值設置爲一個函數,就可以指定事件處理程序,如下所示:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};
這些代碼運行以前不會指定事件處理程序,因此如果這些代碼在頁面中位於按鈕後面,就有可 能在一段時間內怎麼單擊都沒有反應。
使用 DOM0 級方法指定的事件處理程序被認爲是元素的方法。因此,這時候的事件處理程序是在 元素的作用域中運行;換句話說,程序中的 this 引用當前元素。來看一個例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(this.id); //"myBtn"
};
單擊按鈕顯示的是元素的 ID,這個 ID是通過 this.id 取得的。不僅僅是 ID,實際上可以在事件 處理程序中通過 this 訪問元素的任何屬性和方法。以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。 也可以刪除通過 DOM0 級方法指定的事件處理程序,只要像下面這樣將事件處理程序屬性的值設 置爲 null 即可:
btn.onclick = null; //刪除事件處理程序
將事件處理程序設置爲 null 之後,再單擊按鈕將不會有任何動作發生。
如果你使用 HTML指定事件處理程序,那麼 onclick 屬性的值就是一個包含着 在同名 HTML特性中指定的代碼的函數。而將相應的屬性設置爲 null,也可以刪除 以這種方式指定的事件處理程序
DOM2級事件處理程序
“DOM2級事件”定義了兩個方法
用於處理指定事件處理程序的操作:
addEventListener()
用於處理刪除事件處理程序的操作:
removeEventListener()
所有 DOM節點中都包含這兩個方法,並且它們都接受 3個參數
要處 理的事件名 || 作爲事件處理程序的函數 || 一個布爾值。
最後這個布爾值參數如果是 true,表示在捕獲 階段調用事件處理程序;如果是 false,表示在冒泡階段調用事件處理程序
要在按鈕上爲 click 事件添加事件處理程序,可以使用下列代碼:
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
上面的代碼爲一個按鈕添加了 onclick 事件處理程序,而且該事件會在冒泡階段被觸發(因爲後一個參數是 false)。與 DOM0級方法一樣,這裏添加的事件處理程序也是在其依附的元素的作用域中運行。使用 DOM2 級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。來看下面的 例子。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
這兩個事件處理程序會按照添加它們的順序觸發,因此首先 會顯示元素的 ID,其次會顯示"Hello world!"消息。
通過 addEventListener()添加的事件處理程序只能使用 removeEventListener()來移除;
移除時傳入的參數與添加處理程序時使用的參數相同。這也意味着通過 addEventListener()添加的匿名函數將無法移除,如下面的例子所示。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
//這裏省略了其他代碼
btn.removeEventListener("click", function(){
//沒有用!
alert(this.id);
}, false);
在這個例子中,我們使用 addEventListener()添加了一個事件處理程序。雖然調用 remove- EventListener()時看似使用了相同的參數,但實際上,第二個參數與傳入 addEventListener()中 的那一個是完全不同的函數。而傳入 removeEventListener()中的事件處理程序函數必須與傳入 addEventListener()中的相同,如下面的例子所示。
var btn = document.getElementById("myBtn");
var handler = function(){
alert(this.id);
};
btn.addEventListener("click", handler, false);
//這裏省略了其他代碼
btn.removeEventListener("click", handler, false); //有效!
重寫後的這個例子沒有問題,是因爲在 addEventListener()和 removeEventListener()中使 用了相同的函數。
流行的先定義函數後關聯事件如下
var handler = function () {
alert(this.id);
};
var btn = document.getElementById("myBtn").addEventListener("click", handler, false);
IE事件處理程序
IE實現了與 DOM中類似的兩個方法:
attachEvent() detachEvent()。這兩個方法接受相同 的兩個參數:
事件處理程序名稱 || 事件處理程序函數
由於 IE8 及更早版本只支持事件冒泡,所以通過 attachEvent()添加的事件處理程序都會被添加到冒泡階段。
要使用 attachEvent()爲按鈕添加一個事件處理程序,可以使用以下代碼。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
注意!attachEvent()的第一個參數是"onclick",而非 DOM的 addEventListener()方法中 的"click"。
在 IE 中使用 attachEvent()與使用 DOM0 級方法的主要區別在於事件處理程序的作用域。在使 用 DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用 attachEvent()方 法的情況下,事件處理程序會在全局作用域中運行,因此 this 等於 window。來看下面的例子。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert(this === window); //true
});
在編寫跨瀏覽器的代碼時,牢記這一區別非常重要。
與 addEventListener()類似,attachEvent()方法也可以用來爲一個元素添加多個事件處理程 序。來看下面的例子。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
btn.attachEvent("onclick", function(){
alert("Hello world!");
});
這裏調用了兩次 attachEvent(),爲同一個按鈕添加了兩個不同的事件處理程序。不過,與 DOM 方法不同的是,這些事件處理程序不是以添加它們的順序執行,而是以相反的順序被觸發。單擊這個例 子中的按鈕,首先看到的是"Hello world!",然後纔是"Clicked"。
使用 attachEvent()添加的事件可以通過 detachEvent()來移除,條件是必須提供相同的參數。 與 DOM方法一樣,這也意味着添加的匿名函數將不能被移除。不過,只要能夠將對相同函數的引用傳 給 detachEvent(),就可以移除相應的事件處理程序。例如:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
btn.attachEvent("onclick", handler);
//這裏省略了其他代碼
btn.detachEvent("onclick", handler);
這個例子將保存在變量 handler 中的函數作爲事件處理程序。因此,後面的 detachEvent()可以 使用相同的函數來移除事件處理程序。
支持 IE事件處理程序的瀏覽器有 IE和 Opera。
跨瀏覽器事件處理程序
爲了以跨瀏覽器的方式處理事件,不少開發人員會使用能夠隔離瀏覽器差異的 JavaScript 庫,還有 一些開發人員會自己開發合適的事件處理的方法。自己編寫代碼其實也不難,只要恰當地使用能力檢測即可。要保證處理事件的代碼能在大多數瀏覽器下一致地運行,只需關 注冒泡階段。
第一個要創建的方法是 addHandler(),它的職責是視情況分別使用 DOM0 級方法、DOM2 級方 法或 IE方法來添加事件。
這個方法屬於一個名叫 EventUtil 的對象,本書將使用這個對象來處理瀏覽 器間的差異。addHandler()方法接受 3個參數:要操作的元素、事件名稱和事件處理程序函數。 與 addHandler()對應的方法是 removeHandler(),它也接受相同的參數。
這個方法的職責是移除之前添加的事件處理程序——無論該事件處理程序是採取什麼方式添加到元素中的,如果其他方法無 效,默認採用 DOM0級方法。
EventUtil 的用法如下所示。
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
這兩個方法首先都會檢測傳入的元素中是否存在 DOM2級方法。
如果存在 DOM2級方法,則使用 該方法:傳入事件類型、事件處理程序函數和第三個參數 false(表示冒泡階段)。
如果存在的是 IE的 方法,則採取第二種方案。注意,爲了在 IE8及更早版本中運行,此時的事件類型必須加上"on"前綴。 後一種可能就是使用 DOM0級方法(在現代瀏覽器中,應該不會執行這裏的代碼)。此時,我們使用 的是方括號語法來將屬性名指定爲事件處理程序,或者將屬性設置爲 null。
可以像下面這樣使用 EventUtil 對象:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//這裏省略了其他代碼
EventUtil.removeHandler(btn, "click", handler);
addHandler()和 removeHandler()沒有考慮到所有的瀏覽器問題,例如在 IE中的作用域問題。 不過,使用它們添加和移除事件處理程序還是足夠了。此外還要注意,DOM0 級對每個事件只支持一 個事件處理程序。好在,只支持 DOM0級的瀏覽器已經沒有那麼多了,因此這對你而言應該不是什麼 問題。
事件對象
在觸發 DOM上的某個事件時,會產生一個事件對象 event,這個對象中包含着所有與事件有關的 信息。包括導致事件的元素、事件的類型以及其他與特定事件相關的信息。例如,鼠標操作導致的事件對象中,會包含鼠標位置的信息,而鍵盤操作導致的事件對象中,會包含與按下的鍵有關的信息。所有 瀏覽器都支持 event 對象,但支持方式不同。
DOM中的事件對象
兼容 DOM的瀏覽器會將一個 event 對象傳入到事件處理程序中。無論指定事件處理程序時使用什 麼方法(DOM0級或 DOM2級),都會傳入 event 對象。來看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.type);
//"click"
};
btn.addEventListener("click", function(event){
alert(event.type);
//"click"
}, false);
這個例子中的兩個事件處理程序都會彈出一個警告框,顯示由 event.type 屬性表示的事件類型。 這個屬性始終都會包含被觸發的事件類型,例如"click"(與傳入 addEventListener()和 removeEventListener()中的事件類型一致)。
在通過 HTML特性指定事件處理程序時,變量 event 中保存着 event 對象。請看下面的例子。
<input type="button" value="Click Me" οnclick="alert(event.type)"/>
以這種方式提供 event 對象,可以讓 HTML特性事件處理程序與 JavaScript函數執行相同的操作。
event 對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方 法也不一樣。不過,所有事件都會有下表列出的成員。
在事件處理程序內部,對象 this 始終等於 currentTarget 的值,而 target 則只包含事件的實 際目標。如果直接將事件處理程序指定給了目標元素,則 this、currentTarget 和 target 包含相同 的值。來看下面的例子
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.currentTarget === this);
//true
alert(event.target === this);
//true
};
這個例子檢測了 currentTarget 和 target 與 this 的值。由於 click 事件的目標是按鈕,因此 這三個值是相等的。如果事件處理程序存在於按鈕的父節點中(例如 document.body),那麼這些值是 不相同的。再看下面的例子。
document.body.onclick = function(event){
alert(event.currentTarget === document.body);
//true
alert(this === document.body);
//true
alert(event.target === document.getElementById("myBtn"));
//true
};
當單擊這個例子中的按鈕時,this 和 currentTarget 都等於 document.body,因爲事件處理程 序是註冊到這個元素上的。然而,target 元素卻等於按鈕元素,因爲它是 click 事件真正的目標。由 於按鈕上並沒有註冊事件處理程序,結果 click 事件就冒泡到了 document.body,在那裏事件纔得到 了處理。
在需要通過一個函數處理多個事件時,可以使用 type 屬性。例如:
var btn = document.getElementById("myBtn");
var handler = function(event){
switch(event.type){
case "click":
alert("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
這個例子定義了一個名爲 handler 的函數,用於處理 3種事件:click、mouseover 和 mouseout。 當單擊按鈕時,會出現一個與前面例子中一樣的警告框。當按鈕移動到按鈕上面時,背景顏色應該會變 成紅色,而當鼠標移動出按鈕的範圍時,背景顏色應該會恢復爲默認值。這裏通過檢測 event.type 屬性,讓函數能夠確定發生了什麼事件,並執行相應的操作。
要阻止特定事件的默認行爲,可以使用 preventDefault()方法。例如,鏈接的默認行爲就是在 被單擊時會導航到其 href 特性指定的 URL。如果你想阻止鏈接導航這一默認行爲,那麼通過鏈接的 onclick 事件處理程序可以取消它,如下面的例子所示。
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
只有 cancelable 屬性設置爲 true 的事件,纔可以使用 preventDefault()來取消其默認行爲。
另外,stopPropagation()方法用於立即停止事件在 DOM 層次中的傳播,即取消進一步的事件 捕獲或冒泡。例如,直接添加到一個按鈕的事件處理程序可以調用 stopPropagation(),從而避免觸 發註冊在 document.body 上面的事件處理程序,如下面的例子所示。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked");
};
對於這個例子而言,如果不調用 stopPropagation(),就會在單擊按鈕時出現兩個警告框。可是, 由於 click 事件根本不會傳播到 document.body,因此就不會觸發註冊在這個元素上的 onclick 事 件處理程序。
事件對象的 eventPhase 屬性,可以用來確定事件當前正位於事件流的哪個階段。如果是在捕獲階 段調用的事件處理程序,那麼 eventPhase 等於 1;如果事件處理程序處於目標對象上,則 event- Phase 等於 2;如果是在冒泡階段調用的事件處理程序,eventPhase 等於 3。這裏要注意的是,儘管 “處於目標”發生在冒泡階段,但 eventPhase 仍然一直等於 2。來看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert(event.eventPhase);
//2
};
document.body.addEventListener("click", function(event){
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event){
alert(event.eventPhase); //3
};
當單擊這個例子中的按鈕時,首先執行的事件處理程序是在捕獲階段觸發的添加到 document.body 中的那一個,結果會彈出一個警告框顯示錶示 eventPhase 的 1。接着,會觸發在按鈕上註冊的事件處 理程序,此時的 eventPhase 值爲 2。後一個被觸發的事件處理程序,是在冒泡階段執行的添加到 document.body 上的那一個,顯示 eventPhase 的值爲 3。而當 eventPhase 等於 2 時,this、target 和 currentTarget 始終都是相等的。
只有在事件處理程序執行期間,event 對象纔會存在;一旦事件處理程序執行完 成,event 對象就會被銷燬。
IE中的事件對象
與訪問 DOM中的 event 對象不同,要訪問 IE中的 event 對象有幾種不同的方式,取決於指定事 件處理程序的方法。在使用 DOM0級方法添加事件處理程序時,event 對象作爲 window 對象的一個 屬性存在。來看下面的例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event.type);
//"click"
};
在此,我們通過 window.event 取得了 event 對象,並檢測了被觸發事件的類型(IE中的 type 屬性與 DOM中的 type 屬性是相同的)。可是,如果事件處理程序是使用 attachEvent()添加的,那 麼就會有一個 event 對象作爲參數被傳入事件處理程序函數中,如下所示。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
alert(event.type); //"click"
});
在像這樣使用 attachEvent()的情況下,也可以通過 window 對象來訪問 event 對象,就像使用 DOM0級方法時一樣。不過爲方便起見,同一個對象也會作爲參數傳遞。
如果是通過HTML特性指定的事件處理程序,那麼還可以通過一個名叫event的變量來訪問event 對象(與 DOM中的事件模型相同)。再看一個例子。
<input type="button" value="Click Me" οnclick="alert(event.type)">
IE的 event 對象同樣也包含與創建它的事件相關的屬性和方法。其中很多屬性和方法都有對應的 或者相關的 DOM屬性和方法。與 DOM的 event 對象一樣,這些屬性和方法也會因爲事件類型的不同 而不同,但所有事件對象都會包含下表所列的屬性和方法。
因爲事件處理程序的作用域是根據指定它的方式來確定的,所以不能認爲 this 會始終等於事件目 標。故而,好還是使用 event.srcElement 比較保險。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert(window.event.srcElement === this); //true };
btn.attachEvent("onclick", function(event){
alert(event.srcElement === this);
//false
});
在第一個事件處理程序中(使用 DOM0 級方法指定的),srcElement 屬性等於 this,但在第二 個事件處理程序中,這兩者的值不相同。
如前所述,returnValue 屬性相當於 DOM中的 preventDefault()方法,它們的作用都是取消 給定事件的默認行爲。只要將 returnValue 設置爲 false,就可以阻止默認行爲。來看下面的例子。
var link = document.getElementById("myLink");
link.onclick = function(){
window.event.returnValue = false;
};
這個例子在onclick事件處理程序中使用returnValue達到了阻止鏈接默認行爲的目的。與DOM 不同的是,在此沒有辦法確定事件是否能被取消。
相應地,cancelBubble 屬性與 DOM中的 stopPropagation()方法作用相同,都是用來停止事 件冒泡的。由於 IE不支持事件捕獲,因而只能取消事件冒泡;但 stopPropagatioin()可以同時取消 事件捕獲和冒泡。例如:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function(){
alert("Body clicked");
};
通過在 onclick 事件處理程序中將 cancelBubble 設置爲 true,就可阻止事件通過冒泡而觸發 document.body 中註冊的事件處理程序。結果,在單擊按鈕之後,只會顯示一個警告框。
跨瀏覽器的事件對象
雖然 DOM和 IE中的 event 對象不同,但基於它們之間的相似性依舊可以拿出跨瀏覽器的方案來。 IE 中 event 對象的全部信息和方法 DOM 對象中都有,只不過實現方式不一樣。不過,這種對應關係 讓實現兩種事件模型之間的映射非常容易。可以對前面介紹的 EventUtil 對象加以增強,添加如下方 法以求同存異。
var EventUtil = {
addHandler: function(element, type, handler){
//省略的代碼
},
getEvent: function(event){
return event ? event : window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
preventDefault: function(event){
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler){
//省略的代碼
},
stopPropagation: function(event){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
以上代碼顯示,我們爲 EventUtil 添加了 4個新方法。第一個是 getEvent(),它返回對 event 對象的引用。考慮到 IE中事件對象的位置不同,可以使用這個方法來取得 event 對象,而不必擔心指 定事件處理程序的方式。在使用這個方法時,必須假設有一個事件對象傳入到事件處理程序中,而且要 把該變量傳給這個方法,如下所示。
btn.onclick = function(event){
event = EventUtil.getEvent(event);
};
在兼容 DOM的瀏覽器中,event 變量只是簡單地傳入和返回。而在 IE中,event 參數是未定義 的(undefined),因此就會返回 window.event。將這一行代碼添加到事件處理程序的開頭,就可以確 保隨時都能使用 event 對象,而不必擔心用戶使用的是什麼瀏覽器。 第二個方法是 getTarget(),它返回事件的目標。
在這個方法內部,會檢測 event 對象的 target 屬性,如果存在則返回該屬性的值;否則,返回 srcElement 屬性的值。可以像下面這樣使用這個方法。
btn.onclick = function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
};
第三個方法是 preventDefault(),用於取消事件的默認行爲。在傳入 event 對象後,這個方法 會檢查是否存在 preventDefault()方法,如果存在則調用該方法。如果 preventDefault()方法不 存在,則將 returnValue 設置爲 false。下面是使用這個方法的例子。
var link = document.getElementById("myLink");
link.onclick = function(event){
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
以上代碼可以確保在所有瀏覽器中單擊該鏈接都不會打開另一個頁面。首先,使用 EventUtil. getEvent()取得 event 對象,然後將其傳入到 EventUtil.preventDefault()以取消默認行爲。 第四個方法是 stopPropagation(),其實現方式類似。首先嚐試使用 DOM方法阻止事件流,否 則就使用 cancelBubble 屬性。下面看一個例子。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
alert("Body clicked");
};
在此,首先使用 EventUtil.getEvent()取得了 event 對象,然後又將其傳入到 EventUtil. stopPropagation()。別忘了由於 IE不支持事件捕獲,因此這個方法在跨瀏覽器的情況下,也只能用 來阻止事件冒泡。