事件javascript
HTML元素事件是瀏覽器內在自動產生的,當有事件發生時html元素會向外界(這裏主要指元素事件的訂閱者)發出各類事件,如click,onmouseover,onmouseout等等。html
DOM事件流java
DOM(文檔對象模型)結構是一個樹型結構,當一個HTML元素產生一個事件時,該事件會在元素結點與根結點之間的路徑傳播,路徑所通過的結點都會收到該事件,這個傳播過程可稱爲DOM事件流。瀏覽器
主流瀏覽器的事件模型函數
早在2004前在HTML元素事件的訂閱,發送,傳播,處理模型上各瀏覽器實現並不一致,直到DOM Level3中規定後,多數主流瀏覽器才陸陸續續支持DOM標準的事件處理模型 — 捕獲型與冒泡型。
目前除IE瀏覽器外,其它主流的Firefox, Opera, Safari都支持標準的DOM事件處理模型。IE仍然使用本身的模型,即冒泡型,它模型的一部份被DOM採用,這點對於開發者來講也是有好處的,只使用DOM標準,IE都共有的事件處理方式纔能有效的跨瀏覽器。性能
冒泡型事件(Bubbling)this
這是IE瀏覽器對事件模型的實現,也是最容易理解的,至少筆者以爲比較符合實際的。冒泡,顧名思義,事件像個水中的氣泡同樣一直往上冒,直到頂端。從DOM樹型結構上理解,就是事件由葉子結點沿祖先結點一直向上傳遞直到根結點;從瀏覽器界面視圖HTML元素排列層次上理解就是事件由具備從屬關係的最肯定的目標元素一直傳遞到最不肯定的目標元素.操作系統
捕獲型事件(Capturing)code
Netscape Navigator的實現,它與冒泡型恰好相反,由DOM樹最頂層元素一直到最精確的元素,這個事件模型對於開發者來講(至少是我..)有點費解,由於直觀上的理解應該如同冒泡型,事件傳遞應該由最肯定的元素,即事件產生元素開始。
但這個模型在某些狀況下也是頗有用的,接下來會講解到。server
DOM標準事件模型
由於兩個不一樣的模型都有其優勢和解釋,DOM標準支持捕獲型與冒泡型,能夠說是它們二者的結合體。它能夠在一個DOM元素上綁定多個事件處理器,而且在處理函數內部,this關鍵字仍然指向被綁定的DOM元素,另外處理函數參數列表的第一個位置傳遞事件event對象。
首先是捕獲式傳遞事件,接着是冒泡式傳遞,因此,若是一個處理函數既註冊了捕獲型事件的監聽,又註冊冒泡型事件監聽,那麼在DOM事件模型中它就會被調用兩次。
註冊與移除事件監聽器
註冊事件監聽器,或又稱訂閱事件,當元素事件發生時瀏覽器回調該監聽函數執行事件處理。目前主流瀏覽器中有兩種註冊事件的方法,一種是IE瀏覽器的,另外一種是DOM標準的。
1.直接JS或HTML掛載法
<div onclick="alert(this.innerHTML);"> element.onclick = function(){alert(this.innerHTML);}
移除時將事件屬性設爲nul便可,這個也是最經常使用的方法了,優缺點也是顯然的:
a.簡單方便,在HTML中直接書寫處理函數的代碼塊,在JS中給元素對應事件屬性賦值便可
b. IE與DOM標準都支持的一種方法,它在IE與DOM標準中都是在事件冒泡過程當中被調用的。
c.能夠在處理函數塊內直接用this引用註冊事件的元素
d.要給元素註冊多個監聽器,就不能用這方法了
2. IE下注冊多個事件監聽器與移除監聽器方法
IE瀏覽器中HTML元素有個attachEvent方法容許外界註冊該元素多個事件監聽器,例如
element.attachEvent('onclick', observer);
attachEvent接受兩個參數。第一個參數是事件名稱,第二個參數observer是回調處理函數。這裏得說明一下,有個常常會出錯的地方,IE下利用attachEvent註冊的處理函數調用時this指向再也不是先前註冊事件的元素,這時的this爲window對象了,筆者很奇怪IE爲何要這麼作,徹底看不出好處所在。
要移除先前註冊的事件的監聽器,調用element的detachEvent方法便可,參數相同。
element.detachEvent('onclick', observer);
3. DOM標準下注冊多個事件監聽器與移除監聽器方法
實現DOM標準的瀏覽器與IE瀏覽器中註冊元素事件監聽器方式有所不一樣,它經過元素的addEventListener方法註冊,該方法既支持註冊冒泡型事件處理,又支持捕獲型事件處理。
element.addEventListener('click', observer, useCapture);
addEventListener方法接受三個參數。第一個參數是事件名稱,值得注意的是,這裏事件名稱與IE的不一樣,事件名稱是沒’on’開頭的;第二個參數observer是回調處理函數;第三個參數註明該處理回調函數是在事件傳遞過程當中的捕獲階段被調用仍是冒泡階段被調用
移除已註冊的事件監聽器調用element的removeEventListener便可,參數不變.
element.removeEventListener('click', observer, useCapture);
跨瀏覽器的註冊與移除元素事件監聽器方案
弄清楚DOM標準與IE的註冊元素事件監聽器之間的異同後,就能夠實現一個跨瀏覽器的註冊與移除元素事件監聽器方案:
01.//註冊 02.function addEventHandler(element, evtName, callback, useCapture) { 03. //DOM標準 04. if (element.addEventListener) { 05. element.addEventListener(evtName, callback, useCapture); 06. }else { 07. //IE方式,忽略useCapture參數 08. element.attachEvent('on' + evtName, callback); 09. } 10.} 11.//移除 12.//註冊 13.function removeEventHandler(element, evtName, callback, useCapture) { 14. //DOM標準 15. if (element.removeEventListener) { 16. element.removeEventListener(evtName, callback, useCapture); 17. }else { 18. //IE方式,忽略useCapture參數 19. element.dettachEvent('on' + evtName, callback); 20. } 21.}
如何取消瀏覽器事件的傳遞與事件傳遞後瀏覽器的默認處理
先說明取消事件傳遞與瀏覽器事件傳遞後的默認處理是兩個不一樣的概念,可能不少同窗朋友分不清,或者根本不存在這兩個概念。
取消事件傳遞是指,中止捕獲型事件或冒泡型事件的進一步傳遞。例如上圖中的冒泡型事件傳遞中,在body處理中止事件傳遞後,位於上層的document的事件監聽器就再也不收到通知,再也不被處理。
事件傳遞後的默認處理是指,一般瀏覽器在事件傳遞並處理完後會執行與該事件關聯的默認動做(若是存在這樣的動做)。例如,若是表單中input type 屬性是 「submit」,點擊後在事件傳播完瀏覽器就就自動提交表單。又例如,input 元素的 keydown 事件發生並處理後,瀏覽器默認會將用戶鍵入的字符自動追加到 input 元素的值中。
要取消瀏覽器的件傳遞,IE與DOM標準又有所不一樣。
在IE下,經過設置event對象的cancelBubble爲true便可。
1.function someHandle() { 2. window.event.cancelBubble = true; 3.}
DOM標準經過調用event對象的stopPropagation()方法便可。
1.function someHandle(event) { 2. event.stopPropagation(); 3.}
因些,跨瀏覽器的中止事件傳遞的方法是:
1.function someHandle(event) { 2. event = event || window.event; 3. if(event.stopPropagation) 4. event.stopPropagation(); 5. else event.cancelBubble = true; 6.}
取消事件傳遞後的默認處理,IE與DOM標準又不所不一樣。
在IE下,經過設置event對象的returnValue爲false便可。
1.function someHandle() { 2. window.event.returnValue = false; 3.}
DOM標準經過調用event對象的preventDefault()方法便可。
1.function someHandle(event) { 2. event.preventDefault(); 3.}
因些,跨瀏覽器的取消事件傳遞後的默認處理方法是:
1.function someHandle(event) { 2. event = event || window.event; 3. if(event.preventDefault) 4. event.preventDefault(); 5. else event.returnValue = false; 6.}
捕獲型事件模型與冒泡型事件模型的應用場合
標準事件模型爲咱們提供了兩種方案,可能不少朋友分不清這兩種不一樣模型有啥好處,爲何不僅採起一種模型。
這裏拋開IE瀏覽器討論(IE只有一種,無法選擇)什麼狀況下適合哪一種事件模型。
1. 捕獲型應用場合
捕獲型事件傳遞由最不精確的祖先元素一直到最精確的事件源元素,傳遞方式與操做系統中的全局快捷鍵與應用程序快捷鍵類似。當一個系統組合鍵發生時,若是註冊了系統全局快捷鍵監聽器,該事件就先被操做系統層捕獲,全局監聽器就先於應用程序快捷鍵監聽器獲得通知,也就是全局的先得到控制權,它有權阻止事件的進一步傳遞。因此捕獲型事件模型適用於做全局範圍內的監聽,這裏的全局是相對的全局,相對於某個頂層結點與該結點全部子孫結點造成的集合範圍。
例如你想做全局的點擊事件監聽,相對於document結點與document下全部的子結點,在某個條件下要求全部的子結點點擊無效,這種狀況下冒泡模型就解決不了了,而捕獲型卻很是適合,能夠在最頂層結點添加捕獲型事件監聽器,僞碼以下:
1.functionglobalClickListener(event) { 2. if(canEventPass == false) { 3.//取消事件進一步向子結點傳遞和冒泡傳遞 4.event.stopPropagation(); 5.//取消瀏覽器事件後的默認執行 6.event.preventDefault(); 7.} 8.}
這樣一來,當canEventPass條件爲假時,document下全部的子結點click註冊事件都不會被瀏覽器處理。
2. 冒泡型的應用場合
能夠說咱們平時用的都是冒泡事件模型,由於IE只支持這模型。這裏仍是說說,在恰當利用該模型能夠提升腳本性能。在元素一些頻繁觸發的事件中,如onmousemove, onmouseover,onmouseout,若是明確事件處理後不必進一步傳遞,那麼就能夠大膽的取消它。此外,對於子結點事件監聽器的處理會對父層監聽器處理形成負面影響的,也應該在子結點監聽器中禁止事件進一步向上傳遞以消除影響。
綜合案例分析
最後結合下面HTML代碼做分析:
01.<body onclick="alert('current is body');"> 02. <div id="div0" onclick="alert('current is '+this.id)"> 03. <div id="div1" onclick="alert('current is '+this.id)"> 04. <div id="div2"> 05. <div id="event_source" 06. onclick="alert('current is '+this.id)" 07. style="height:200px;width:200px;background-color:red;"> 08. </div> 09. </div> 10. </div> 11. </div> 12.</body>
HTML運行後點擊紅色區域,這是最裏層的DIV,根據上面說明,不管是DOM標準仍是IE,直接寫在html裏的監聽處理函數是事件冒泡傳遞時調用的,由最裏層一直往上傳遞,因此會前後出現
current is event_source
current is div2
current is div1
current is div0
current is body
添加如下片斷:
1.var div2 = document.getElementById('div2'); 2.addEventHandler(div2,'click',function(event){ 3. event = event || window.event; 4. if(event.stopPropagation) 5. event.stopPropagation(); 6. else event.cancelBubble = true; 7.},false);
當點擊紅色區域後,根據上面說明,在泡冒泡處理期間,事件傳遞到div2後被中止傳遞了,因此div2上層的元素收不到通知,因此會前後出現:
current is event_source
current is div2
在支持DOM標準的瀏覽器中,添加如下代碼:
1.document.body.addEventListener('click',function(event){ 2. event.stopPropagation(); 3.},true);
以上代碼中的監聽函數因爲是捕獲型傳遞時被調用的,因此點擊紅色區域後,雖然事件源是ID爲event_source的元素,但捕獲型選傳遞,從最頂層開始,body結點監聽函數先被調用,而且取消了事件進一步向下傳遞,因此只會出現current is body.