DOM 事件模型

事件

HTML元素事件是瀏覽器內在自動產生的,當有事件發生時html元素會向外界(這裏主要指元素事件的訂閱者)發出各種事件,如click,onmouseover,onmouseout等等。

DOM事件流

DOM(文檔對象模型)結構是一個樹型結構,當一個HTML元素產生一個事件時,該事件會在元素結點與根結點之間的路徑傳播,路徑所經過的結點都會收到該事件,這個傳播過程可稱爲DOM事件流。

主流瀏覽器的事件模型

早在2004前在HTML元素事件的訂閱,發送,傳播,處理模型上各瀏覽器實現並不一致,直到DOM Level3中規定後,多數主流瀏覽器才陸陸續續支持DOM標準的事件處理模型 — 捕獲型與冒泡型。
目前除IE瀏覽器外,其它主流的Firefox, Opera, Safari都支持標準的DOM事件處理模型。IE仍然使用自己的模型,即冒泡型,它模型的一部份被DOM採用,這點對於開發者來說也是有好處的,只使用DOM標準,IE都共有的事件處理方式纔能有效的跨瀏覽器。

冒泡型事件(Bubbling)

這是IE瀏覽器對事件模型的實現,也是最容易理解的,至少筆者覺得比較符合實際的。冒泡,顧名思義,事件像個水中的氣泡一樣一直往上冒,直到頂端。從DOM樹型結構上理解,就是事件由葉子結點沿祖先結點一直向上傳遞直到根結點;從瀏覽器界面視圖HTML元素排列層次上理解就是事件由具有從屬關係的最確定的目標元素一直傳遞到最不確定的目標元素.

捕獲型事件(Capturing)

Netscape Navigator的實現,它與冒泡型剛好相反,由DOM樹最頂層元素一直到最精確的元素,這個事件模型對於開發者來說(至少是我..)有點費解,因爲直觀上的理解應該如同冒泡型,事件傳遞應該由最確定的元素,即事件產生元素開始。
但這個模型在某些情況下也是很有用的,接下來會講解到。

DOM標準事件模型

因爲兩個不同的模型都有其優點和解釋,DOM標準支持捕獲型與冒泡型,可以說是它們兩者的結合體。它可以在一個DOM元素上綁定多個事件處理器,並且在處理函數內部,this關鍵字仍然指向被綁定的DOM元素,另外處理函數參數列表的第一個位置傳遞事件event對象。

首先是捕獲式傳遞事件,接着是冒泡式傳遞,所以,如果一個處理函數既註冊了捕獲型事件的監聽,又註冊冒泡型事件監聽,那麼在DOM事件模型中它就會被調用兩次。

註冊與移除事件監聽器

註冊事件監聽器,或又稱訂閱事件,當元素事件發生時瀏覽器回調該監聽函數執行事件處理。目前主流瀏覽器中有兩種註冊事件的方法,一種是IE瀏覽器的,另一種是DOM標準的。

1.直接JS或HTML掛載法

<div onclick="alert(this.innerHTML);"></div>
  element.onclick = function(){alert(this.innerHTML);}

移除時將事件屬性設爲nul即可,這個也是最常用的方法了,優缺點也是顯然的:

  • 簡單方便,在HTML中直接書寫處理函數的代碼塊,在JS中給元素對應事件屬性賦值即可
  • IE與DOM標準都支持的一種方法,它在IE與DOM標準中都是在事件冒泡過程中被調用的。
  • 可以在處理函數塊內直接用this引用註冊事件的元素
  • 要給元素註冊多個監聽器,就不能用這方法了

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的註冊元素事件監聽器之間的異同後,就可以實現一個跨瀏覽器的註冊與移除元素事件監聽器方案:

  //註冊
function addEventHandler(element, evtName, callback, useCapture) {
//DOM標準
if (element.addEventListener) {
element.addEventListener(evtName, callback, useCapture);
} else {
//IE方式,忽略useCapture參數
element.attachEvent('on' + evtName, callback);
}
}
 
//移除
//註冊
function removeEventHandler(element, evtName, callback, useCapture) {
//DOM標準
if (element.removeEventListener) {
element.removeEventListener(evtName, callback, useCapture);
} else {
//IE方式,忽略useCapture參數
element.dettachEvent('on' + evtName, callback);
}
}

如何取消瀏覽器事件的傳遞與事件傳遞後瀏覽器的默認處理

先說明取消事件傳遞與瀏覽器事件傳遞後的默認處理是兩個不同的概念,可能很多同學朋友分不清,或者根本不存在這兩個概念。

取消事件傳遞是指,停止捕獲型事件或冒泡型事件的進一步傳遞。例如上圖中的冒泡型事件傳遞中,在body處理停止事件傳遞後,位於上層的document的事件監聽器就不再收到通知,不再被處理。

事件傳遞後的默認處理是指,通常瀏覽器在事件傳遞並處理完後會執行與該事件關聯的默認動作(如果存在這樣的動作)。例如,如果表單中input type 屬性是 “submit”,點擊後在事件傳播完瀏覽器就就自動提交表單。又例如,input 元素的 keydown 事件發生並處理後,瀏覽器默認會將用戶鍵入的字符自動追加到 input 元素的值中。

要取消瀏覽器的件傳遞,IE與DOM標準又有所不同。

在IE下,通過設置event對象的cancelBubble爲true即可。

  function someHandle() {
window.event.cancelBubble = true;
}

DOM標準通過調用event對象的stopPropagation()方法即可。

  function someHandle(event) {
event.stopPropagation();
}

因些,跨瀏覽器的停止事件傳遞的方法是:

  function someHandle(event) {
event = event || window.event;
if(event.stopPropagation)
event.stopPropagation();
else event.cancelBubble = true;
}

取消事件傳遞後的默認處理,IE與DOM標準又不所不同。

在IE下,通過設置event對象的returnValue爲false即可。

  function someHandle() {
window.event.returnValue = false;
}

DOM標準通過調用event對象的preventDefault()方法即可。

  function someHandle(event) {
event.preventDefault();
}

因些,跨瀏覽器的取消事件傳遞後的默認處理方法是:

  function someHandle(event) {
event = event || window.event;
if(event.preventDefault)
event.preventDefault();
else event.returnValue = false;
}

捕獲型事件模型與冒泡型事件模型的應用場合

標準事件模型爲我們提供了兩種方案,可能很多朋友分不清這兩種不同模型有啥好處,爲什麼不只採取一種模型。
這裏拋開IE瀏覽器討論(IE只有一種,沒法選擇)什麼情況下適合哪種事件模型。

1. 捕獲型應用場合

捕獲型事件傳遞由最不精確的祖先元素一直到最精確的事件源元素,傳遞方式與操作系統中的全局快捷鍵與應用程序快捷鍵相似。當一個系統組合鍵發生時,如果註冊了系統全局快捷鍵監聽器,該事件就先被操作系統層捕獲,全局監聽器就先於應用程序快捷鍵監聽器得到通知,也就是全局的先獲得控制權,它有權阻止事件的進一步傳遞。所以捕獲型事件模型適用於作全局範圍內的監聽,這裏的全局是相對的全局,相對於某個頂層結點與該結點所有子孫結點形成的集合範圍。

例如你想作全局的點擊事件監聽,相對於document結點與document下所有的子結點,在某個條件下要求所有的子結點點擊無效,這種情況下冒泡模型就解決不了了,而捕獲型卻非常適合,可以在最頂層結點添加捕獲型事件監聽器,僞碼如下:

  function globalClickListener(event) {
if(canEventPass == false) {
//取消事件進一步向子結點傳遞和冒泡傳遞
event.stopPropagation();
//取消瀏覽器事件後的默認執行
event.preventDefault();
}
}

這樣一來,當canEventPass條件爲假時,document下所有的子結點click註冊事件都不會被瀏覽器處理。

2. 冒泡型的應用場合

可以說我們平時用的都是冒泡事件模型,因爲IE只支持這模型。這裏還是說說,在恰當利用該模型可以提高腳本性能。在元素一些頻繁觸發的事件中,如onmousemove, onmouseover,onmouseout,如果明確事件處理後沒必要進一步傳遞,那麼就可以大膽的取消它。此外,對於子結點事件監聽器的處理會對父層監聽器處理造成負面影響的,也應該在子結點監聽器中禁止事件進一步向上傳遞以消除影響。

綜合案例分析

最後結合下面HTML代碼作分析:

<div id="div0" onclick="alert('current is '+this.id)">
<div id="div1" onclick="alert('current is '+this.id)">
<div id="div2" onclick="alert('current is '+this.id)">
<div id="event_source" style="height: 200px; width: 200px; background-color: red;" onclick="alert('current is '+this.id)"></div>
</div>
</div>
</div>

HTML運行後點擊紅色區域,這是最裏層的DIV,根據上面說明,無論是DOM標準還是IE,直接寫在html裏的監聽處理函數是事件冒泡傳遞時調用的,由最裏層一直往上傳遞,所以會先後出現
current is event_source
current is div2
current is div1
current is div0
current is body

添加以下片段:

  var div2 = document.getElementById('div2');
addEventHandler(div2, 'click', function(event){
event = event || window.event;
if(event.stopPropagation)
event.stopPropagation();
else event.cancelBubble = true;
}, false);

當點擊紅色區域後,根據上面說明,在泡冒泡處理期間,事件傳遞到div2後被停止傳遞了,所以div2上層的元素收不到通知,所以會先後出現:
current is event_source
current is div2

在支持DOM標準的瀏覽器中,添加以下代碼:

   document.body.addEventListener('click', function(event){
event.stopPropagation();
}, true);

以上代碼中的監聽函數由於是捕獲型傳遞時被調用的,所以點擊紅色區域後,雖然事件源是ID爲event_source的元素,但捕獲型選傳遞,從最頂層開始,body結點監聽函數先被調用,並且取消了事件進一步向下傳遞,所以只會出現
current is body

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章