1.事件流
事件流描述的是從頁面中接收事件的順序。
有兩種,分別是:事件冒泡流和事件捕獲流。
事件冒泡
IE的事件流叫做事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)。事件捕獲(event capturing)
事件捕獲的思想是不太具體的節(jié)點應該更早接收到事件,而最具體的節(jié)點應該最后接收到事件。事件捕獲的用意在于在事件到達預定目標之前捕獲它。
事件捕獲從IE9和現(xiàn)在瀏覽器才支持他,也就是說支持DOM2事件的瀏覽器才支持他-
DOM事件流
“DOM2級事件”規(guī)定的事件流包括3各階段:事件捕獲階段、處于目標階段、事件冒泡階段。
dom事件流模擬圖
在 DOM 事件流中,實際的目標( <div> 元素)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件從 document 到 <html> 再到 <body> 后就停止了。下一個階段是“處于目標”階段,于是事件在 <div>上發(fā)生,并在事件處理(后面將會討論這個概念)中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生,事件又傳播回文檔。(這里的事件在div上發(fā)生,并在事件處理中被看成冒泡階段的一部分可以這么理解:在“處于目標”階段,事件處理程序就開始執(zhí)行了,那么在事件處理程序里面不是有阻止事件冒泡嘛,你要阻止那么就必須有冒泡開始啊,那就把處于目標階段看成是事件冒泡的一部分,當然只是在事件處理程序里面這么認為,事件流還是分三個階段。)
2.事件處理程序
- HTML 事件處理程序
行內式
<input type='button' value='click me' onclick='alert("haha")' >
- DOM0級事件處理程序
給元素添加自己的事件處理程序屬性。這些屬性通常全部小寫,比如onclick。
var btn = document.getElementById("mybtn");
btn.onclick = function(){
//事件處理程序的函數部分
// this引用當前元素,即btn
}
以這種方式被添加的事件處理程序在事件流的冒泡階段被處理。也可以刪除通過DOM0級方法指定的事件處理程序,只要向下面這樣將事件處理程序屬性的值設為null即可:
btn.onclick = null;
如果你是用的是html指定事件處理程序,那么onclick屬性的值就是一個包含著在同名html特性中指定的代碼的函數。而將相應的屬性設置為null,也可以刪除以這種方式指定的事件處理程序。(這個就是說行內式綁定事件處理程序相當于dom0級方法指定的事件處理程序,可以用dom0級的方法去獲取或換行內式綁定的同名事件的處理程序)。
- DOM2級事件處理程序
兩個方法:addEventListener(),removeEventListener()。
所有的DOM節(jié)點都包含這兩個方法,他們都接受三個參數:要處理的事件名(沒有on哦比如click)、作為事件處理程序的函數和一個布爾值。這個布爾值如果是true,表示在事件捕獲階段調用這個事件處理程序,如果是false,表示在冒泡階段調用事件處理程序。
DOM2級事件處理程序的主要好處是可以添加多個事件處理程序。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
這兩個事件處理程序會按照添加他們的順序觸發(fā)。先顯示元素的ID,再顯示“hello world!”消息。
通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除。在給removeEventListener()傳第二個參數的時候,必須傳入事件處理函數的函數名,不能寫一個內容一樣的匿名函數代替,那樣是不對的,因為那兩個函數是完全不同的函數。
大多數情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達目標之前截獲它的時候將事件處理程序添加到捕獲階段。如果不是特別需要,我們不建議在事件捕獲階段注冊事件處理程序。
- IE事件處理程序
兩個方法:attachEvent()和detachEvent。
接收兩個參數:事件處理程序名稱(有on哦比如'onclick')與事件處理程序函數。由于IE8及跟早版本只支持事件冒泡,所以通過attachEvent()添加的事件處理程序都會被添加到冒泡階段。
在IE中使用attachEvent()與使用DOM0級方法的主要區(qū)別在于事件處理程序的作用域。在使用DOM0級方法的時候,事件處理程序會在其所屬元素的作用域內運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this等于window。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert(this === window); //true
});
在編寫跨瀏覽器的代碼時,牢記這一區(qū)別非常重要。
與 addEventListener() 類似, attachEvent() 方法也可以用來為一個元素添加多個事件處理程序。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
btn.attachEvent("onclick", function(){
alert("Hello world!");
});
這里調用了兩次 attachEvent() ,為同一個按鈕添加了兩個不同的事件處理程序。不過,與 DOM方法不同的是,這些事件處理程序不是以添加它們的順序執(zhí)行,而是以相反的順序被觸發(fā)。單擊這個例子中的按鈕,首先看到的是 "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);
支持IE事件處理程序的瀏覽器只有IE和Opera。
- 跨瀏覽器的事件處理程序
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;
}
}
};
事件對象
事件對象存在于事件處理程序函數中,event對象可以使函數的第一個形參,也是window對象的屬性。所以在通過HTML特性指定事件處理程序時,變量event未經申明就可以使用,因為他保存著event對象,event是window對象的屬性。
事件對象的屬性與方法
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
};
由于click事件的目標是按鈕,因此這三個值是相等的。如果事件處理程序存在于按鈕的父節(jié)點中(例如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則是事件最具體發(fā)生的那個對象,即按鈕。(點擊在按鈕上的)
還有就是事件捕獲只能在DOM2二級事件處理程序里存在,DOM0級和HTML里面綁定的事件都是發(fā)生在事件冒泡或者處于目標階段。
event.type
event.type 指的是事件的類型,如點擊事件,他的值就是'click'。
preventDefault()
想要阻止特定事件的默認行為,可以使用preventDefault()方法,例如阻止連接在被點擊之后默認會跳轉到href特性指定的URL。你可以在事件處理程序函數里面調用event.preventDefault()方法。
stopPropagation()
stopPropagation()方法用于立即停止事件在DOM層次中的傳播,即取消進一步的事件捕獲或冒泡。
看下面的這個例子:
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked");
};
對于這個例子而言,如果不調用 stopPropagation() ,就會在單擊按鈕時出現(xiàn)兩個警告框。可是,由于 click 事件根本不會傳播到 document.body ,因此就不會觸發(fā)注冊在這個元素上的 onclick 事件處理程序。
stopImmediatePropagation()
如果某個元素有多個相同類型事件的事件監(jiān)聽函數,則當該類型的事件觸發(fā)時,多個事件監(jiān)聽函數將按照順序依次執(zhí)行.如果某個監(jiān)聽函數執(zhí)行了 event.stopImmediatePropagation()方法,則除了該事件的冒泡行為被阻止之外(event.stopPropagation方法的作用),該元素綁定的后序相同類型事件的監(jiān)聽函數的執(zhí)行也將被阻止。
eventPhase
事件對象的eventPhase屬性可以用來確定當前事件處于事件流的哪個階段。如果是在捕獲階段調用的事件處理程序,那么eventPhase等于1;如果事件處理程序在處于目標階段調用,那么eventPhase等于2,如果是在冒泡階段調用的事件處理程序,那么eventPhase等于3.這里注意一點:盡管“處于目標”發(fā)生在冒泡階段,但eventPhase仍然一直等于2。發(fā)現(xiàn)沒有:當事件捕獲階段和事件冒泡階段調用事件處理程序的時候,event.target不等于this對象和currentTarget屬性,而只有在eventPhase等于2的時候,這三個才相等。
當事件處理程序綁定的對象和事件發(fā)生的那個具體的對象是同一個對象的時候,那么eventPhase一定等于2,this、currentTarget、target也一定相等。就算這時候用DOM2級事件去綁定事件處理程序,并傳入true,要他在事件捕獲階段調用事件處理程序,他的eventPhase值還是2,因為在DOM事件流中,實際的目標(target)在捕獲階段不會接收到事件。
只有在事件處理程序執(zhí)行期間,event對象才會存在;一旦事件處理程序執(zhí)行完成,event對象就會被銷毀。
事件類型
- load
- unload
- resize
- scroll
客戶區(qū)坐標位置
event.clientX 和event.clientY
頁面坐標位置
event.pageX和event.pageY
在頁面沒有滾動的情況下,pageX和pageY的值與clientX和clientY相等。
在IE8及更早版本不支持事件對象上的頁面坐標,不過使用客戶區(qū)坐標和滾動信息可以計算出來。這時需要用到 document.body (混雜模式)或 document.documentElement (標準模式)中的scrollLeft和scrollTop的屬性。
屏幕坐標位置
event.screenX 和event.screenY
修改鍵
雖然鼠標事件主要是使用鼠標來觸發(fā)的,但在按下鼠標時鍵盤上的某些鍵的狀態(tài)也可以影響到所要采取的操作。這些修改鍵就是 Shift、Ctrl、Alt 和 Meta(在 Windows鍵盤中是 Windows鍵,在蘋果機中是 Cmd 鍵),它們經常被用來修改鼠標事件的行為。DOM 為此規(guī)定了 4 個屬性,表示這些修改鍵的狀態(tài): shiftKey 、 ctrlKey 、 altKey 和 metaKey 。這些屬性中包含的都是布爾值,如果相應的鍵被按下了,則值為 true ,否則值為 false 。
IE9、Firefox、Safari、Chrome和 Opera 都支持這 4 個鍵。IE8 及之前版本不支持metaKey 屬性
相關元素
這個屬性只對mouseover和mouseout事件才包含值。
這個有意思,具體可以看看高程373頁。