在觸發DOM上的某個事件時,會産生一個事件對象event,這個對象中包含着所有與事件有關的資訊。包括導緻事件的元素、事件的類型以及其他與特定事件相關的資訊。例如,滑鼠操作導緻的事件對象中,會包含滑鼠位置的資訊,而鍵盤操作導緻的事件對象中,會包含與按下的鍵有關的資訊。所有浏覽器都支援event 對象,但支援方式不同。
1)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);
在事件處理程式内部,對象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
};
如果事件處理程式存在于按鈕的父節點中(例如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,在那裡事件才得到了處理。
this 和currentTarget 指向的是事件處理程式注冊的元素;而target指向的是事件真正目标。
在需要通過一個函數處理多個事件時,可以使用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 對象就會被銷毀。
2)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" onclick="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 中注冊的事件處理程式。結果,在單擊按鈕之後,隻會顯示一個警告框。
3)跨浏覽器的事件對象
雖然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 不支援事件捕獲,是以這個方法在跨浏覽器的情況下,也隻能用來阻止事件冒泡。