事件
JavaScript與HTML之間的交互是通過事件實現的。事件,就是文檔或瀏覽器窗口中發生的一些特定的交互瞬間,通過監聽特定事件的發生,你能響應相關的操作。圖片引用:UI Events
事件流
主要是當時的IE團隊提出的事件流逝是事件冒泡流,而Netscape提出的是事件捕獲流, 可以使用DOM2級定義的addEventListener()方法來處理在冒泡或者捕獲階段調用事件處理程序。
事件冒泡
即事件開始時由最具體的元素(文檔中嵌套最深的節點)接收,然后逐級向上傳播到較為不具體的節點。
事件捕獲
即最外層的元素更早接收到事件,而最具體的元素應該最后接收到事件。事件捕獲的用意在於在事件到達預定目標之前捕獲它。
DOM事件流
而“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段,處於目標階段和事件冒泡階段。首先發生的是事件捕獲,然后是實際的目標接收到事件,最后一個階段是冒泡階段。
addEventListener函數接收三個參數,要處理的事件名、作為事件處理程序的函數和一個布爾值,最后的布爾值如果是true,表示在不回階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。
事件捕獲:
//html <!DOCTYPE html> <html> <head> <title></title> </head> <body> <div class="a"> <div class="b"> <button class="c">click</button> </div> </div> </body> </html> var $body = document.querySelector("body"), $a = document.querySelector(".a"), $b = document.querySelector(".b"), $c = document.querySelector(".c"); $body.addEventListener("click",function(){ console.log(this); },true); $a.addEventListener("click",function(){ console.log(this); },true); $b.addEventListener("click",function(){ console.log(this); },true); $c.addEventListener("click",function(){ console.log(this); },true);
單擊button,則
事件冒泡:
$body.addEventListener("click",function(){ console.log(this); },false); $a.addEventListener("click",function(){ console.log(this); },false); $b.addEventListener("click",function(){ console.log(this); },false); $c.addEventListener("click",function(){ console.log(this); },false);
單擊button,則
上述代碼在IE9和其他現代瀏覽器都支持(測試頁)。IE8及更早版本不支持DOM事件流只有事件冒泡。所以我們有兩個階段可以在目標對象上操作事件,但為了兼容性考慮,一般用冒泡的情況比較多。
事件處理程序
事件是用戶或瀏覽器自身執行的某種動作,而響應某個事件的函數就叫做事件處理程序(或事件偵聽器)。
HTML事件處理程序
即內聯式處理函數,以"on"開頭:
<button onclick="alert(this)"></button>
雖然方便,但有很大問題,首先,存在時差問題,因為用戶可能會在HTML元素一出現在頁面上就觸發相應的事件,但當時的事件處理程序有可能尚不具備執行條件;另一個缺點是:這樣擴展事件處理程序的作用域鏈在不同瀏覽器會導致不同結果;而最后的缺點則是HTML與JavaScript代碼緊密耦合,如果要更換事件處理程序,就要改動兩個地方。
DOM0級事件處理程序
通過JavaScript指定事件處理程序的傳統方式,就是將一個函數賦值給一個事件處理程序屬性。 每個元素(包括window和docuemnt)都有自己的事件處理程序屬性,這些屬性通常全部小寫:
var btn = document.getElementById('myBtn'); btn.onclick = function(){ alert("Clicked"); }
以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
DOM2級事件處理程序
“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程序的操作:addEventListener()和removeEventListener()。所有DOM節點中都包含這兩個方法,在上面的事件捕獲和冒泡有用到。使用DOM2級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。
IE事件處理程序
IE實現的方法是:attachEvent()和detachEvent()。這兩個方法接收相同的兩個參數:事件處理程序名稱與事件處理程序函數。通過attachEvent()添加的事件處理程序都會被添加到冒泡階段:
var btn = document.getElementById("myBtn"); btn.attachEvent("onclick",function(){ alert("Clicked"); })
這里的參數中表行為的是onclick而非DOM的addEventListener()方法中的click,在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程序的作用域,在使用DOM0級方法的情況下,事件處理程序會在其所屬元素的作用域內運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this等於window
var btn = document.getElementById("myBtn"); btn.attachEvent('onclick',function(){ alert(this === window);// true });
事件對象
在觸發DOM上的某個事件時,會產生一個事件對象event,所有瀏覽器都支持event對象,但支持方式不同。
DOM中的事件對象
event對象包含與創建它的特定事件有關的屬性和方法。觸發的事件類型不一樣,可用的屬性和方法也不一樣。不過,所有事件都有下表的成員屬性。
屬性/方法 | 類型 | 讀/寫 | 說明 |
---|---|---|---|
bubbles | boolean | 只讀 | 表明事件是否冒泡 |
cancelable | boolean | 只讀 | 表明是否可以取消事件的默認行為 |
currentTarget | element | 只讀 | 其事件處理程序當前正在處理事件的那個元素 |
defaultPrevented | boolean | 只讀 | 為true表示已經調用了preventDefault()DOM3新增 |
datail | integer | 只讀 | 與事件相關的細節信息 |
eventPhase | integer | 只讀 | 調用事件處理程序的階段:1表示捕獲階段,2表示處於目標 3表示冒泡階段 |
preventDefault() | function | 只讀 | 取消事件的默認行為。如果cancelable是true,則可以使用這個方法 |
stopImmediatePropagation() | function | 只讀 | 取消事件的進一步捕獲或冒泡,同時阻止任何事件處理程序被調用(DOM3級新增) |
stopPropagation() | function | 只讀 | 取消事件的進一步捕獲或冒泡,如果bubbles為true,則可以使用這個方法 |
target | element | 只讀 | 事件的目標 |
isTrusted | boolean | 只讀 | 返回一個布爾值,表明當前事件是否是由用戶行為觸發,還是由一個腳本生成的 |
type | string | 只讀 | 被觸發的事件的類型 |
view | AbstractView | 只讀 | 與事件關聯的抽象視圖。等同於發生事件的window對象 |
更詳細的文檔可以看 這里
在事件處理程序內部,對象this始終等於currentTarget的值,而target則只包含事件的實際目標。如果直接將事件處理程序指定給了目標元素,則this,currentTarget和target包含相同的值。
根據event所擁有的屬性,就可以更加細致的操作事件。在w3c的這篇Events中,我們可以知道哪些事件可以冒泡. 首先是冒泡事件,如果只需要在具體節點觸發,而不冒泡到包裹層中,則可以使用與冒泡行為相關的一系列屬性,舉例:
<div class="a"> <a href="http://www.cnblogs.com"></a> </div>
在這個div中,如果div和a都有監聽點擊事件,那么在點擊a標簽的時候(默認事件流在已過捕獲階段),會先觸發a的點擊事件,然后是div的點擊事件,最后是a標簽的默認事件。如果不想冒泡,也就是說點擊a時只觸發a的點擊事件,則可以使用stopPropagation()方法:
var $a = document.querySelector("a"); $a.addEventListener("click",function(event){ if(event.bubbles){ event.stopPropagation(); } });
也可以在處理程序里直接刪除節點,也能阻止冒泡。 當然,有時我們不希望跳轉頁面,而是執行自定義的腳本,那就可以使用preventDefault()方法:
var $a = document.querySelector("a"); $a.addEventListener("click",function(event){ if(event.bubbles){ event.preventDefault(); // do something } });
有時我們可能會接手他人的代碼,在不了解具體代碼的情況下,如果只是單個獨立事件需要修改,則可以在運行老的js代碼之前,使用stopPropagation()方法。
$a.addEventListener('click',function(event){ event.preventDefault(); event.stopImmediatePropagation(); // do something }) $a.addEventListener("click",function(event){ alert("這是a標簽"); });
當然,冒泡並不是說是一件壞事,因為場景很多變,在某些場景下,冒泡也是很有用的,比如說事件代理(delegation)。 有時我們需要對生成的節點附加操作,event總是需要先找到element,再進行注冊,才可以進行事件監聽,那么對於這些生成的節點來說,當然可以在生成之后再次對該節點重新注冊事件。但如果是生成了同一個標記的節點呢? 那么我們不僅需要把之前注冊的事件remove,還需要重新綁定之前的標記,否則會二次觸發。這時候事件代理 就可以解決這一類問題。根據DOM事件流,我們可以知道,在節點冒泡時,可以一直往上冒泡,所以,我們完全可以把事件代理給點擊節點的父節點來做,根據event.target屬性,我們可以知道指向觸發事件的對象到底是誰,也因此,不用再使用繁瑣的重新注冊事件,就可以直接操作子元素。像jQuery,Zepto中都有關於事件代理的處理方法,后篇會繼續涉及到。
$div.addEventListener("click",function(event){ alert("這是外層"); if(event.target) { event.target.style.visibility = 'hidden'; } });
冒泡也會有副作用,mouseout事件會導致離開內部節點時提前觸發,為此,jQuery自定義了mouseenter和mouseleave事件,Zepto也有相應的事件。 只有在事件處理程序執行期間,event對象才會存在;一旦事件處理程序執行完成,event對象就會被銷毀(閉包)
IE中的事件對象
在IE中訪問event,特別是IE6~8尤其注意,在使用DOM0級方法添加事件處理程序時,event對象作為window對象的一個屬性存在。
var btn = document.getElementById('a'); btn.onclick = function(){ var event = window.event; alert(event.type); //"click" }
而IE中的event對象都會包含下表的屬性和方法:
屬性/方法 | 類型 | 讀/寫 | 說明 |
---|---|---|---|
cancelBubble | Boolean | 讀/寫 | 默認值為false,但將其設置為true就可以取消事件冒泡(與DOM中的stopPropagation()方法的作用相同) |
returnValue | Boolean | 讀/寫 | 默認值為false,但將其設置為false就可以取消事件的默認行為(與DOM中的preventDefault()方法的作用相同) |
srcElement | Element | 只讀 | 事件的目標(與DOM中的target屬性相同) |
type | String | 只讀 | 被觸發的事件的類型 |
事件類型
不表
事件模擬
結合jQuery和Zepto源碼總結。
部分用例:event