1. 程式人生 > >事件捕獲與事件冒泡

事件捕獲與事件冒泡

點擊 web 不想 dhtml 參考 model 兼容 dom style

一、背景

假設有一個HTML代碼片段如下:

<div id="div">
    <input type="button" value="點擊測試"></input>
</div>

如果我們同時給 div 元素和 input 元素註冊 click 事件,當點擊 input 元素時,哪個事件先執行?

要回答這個問題,先得明白:

HTML文檔是層級嵌套結構,頁面元素處理事件時,總是最外層元素最先捕獲到事件,再層層向下傳遞給子元素。這稱為事件捕獲階段。

最裏層子元素接收到事件後,再層層向上傳遞給父元素。這是事件的冒泡階段。

兩個階段都可以處理我們感興趣的事件,這就是下文介紹的事件註冊模式。

二、事件註冊模式

1、內聯模式

<input type="button" value="點擊測試" onlcick="alert(‘click on btn.‘)"></input>

這是最古老的事件註冊模式,事件處理函數作為 HTML 元素屬性被添加,由網景(Netscape)發明,除 IE3 for Mac 外所有瀏覽器都支持。不推薦使用。

2、傳統模式

隨著 DHTML 的出現,我們處理 web 頁面的方式被徹底改變,事件註冊模式必須變得靈活多樣,以適應這種改變。於是瀏覽器廠商推出了新的事件註冊模式。由於網景最先推出該模式,從而成為事實上的標準,後續包括 IE 在內的所有瀏覽器都支持該標準。寫法如下:

// 添加事件處理操作
element.onclick = function(){ alert("click event!"); };

// 移除事件處理操作
element.onclick = null;

3、高級模式

為了解決傳統模式的不足,微軟推出了自己的事件註冊模式,同時 W3C 也在 DOM2 規範中給出了註冊模式。於是就有兩種註冊模式。

1)W3C模式

// 添加一個事件處理函數
element.addEventListener("click", function(){
    //xxx
}, false);

// 添加兩個事件處理函數
element.addEventListener("click", doOne, false
); element.addEventListener("click", doTwo, false); // 移除事件處理函數 element.removeEventListener("click", doOne, false);

W3C 模式接收 3 個參數,分別為事件類型、事件處理函數和事件處理階段。說明如下:

  • 事件處理階段參數,true=事件捕獲,false=事件冒泡,你不確定的話,直接 false。
  • 事件處理函數中的 this 關鍵字即為元素自身。
  • 在 DOM3 規範中,新增了eventListenerList 屬性,記錄當前註冊到該元素上的事件處理函數。
  • 即使使用 removeEventListener 方法移除一個未綁定的事件操作,也不會報錯。

2)微軟模式

// 添加一個事件操作
element.attachEvent("onclick", function(){
    //do something
});

// 添加兩個事件操作
element.attachEvent("onclick", function(){
    // do something
});
element.attachEvent("onclick", handler);

// 移除事件操作
element.detach("onclick", handler);

不足:

》事件只能冒泡,不能捕獲;

》事件處理函數是被引用,而不是復制,所以 this 關鍵字總是 window,完全沒用。

以上兩個不足導致的後果是,當事件冒泡時,我們無法知道當前是哪個元素在處理該事件。

三、回到問題

在 W3C 標準未出之前,網景采用“事件捕獲”方式處理事件順序,微軟則采用“事件冒泡”方式。這裏僅對 W3C 標準做說明。

W3C 標準兼容兩種方式,將事件處理過程分作兩個階段:事件捕獲階段和事件冒泡階段。如下圖所示,註冊在事件捕獲階段的事件處理函數在捕獲階段執行,註冊在冒泡階段的處理函數在冒泡階段執行。

                       / \
----------| 捕 |------| 冒 |----------- | div | 獲 | | 泡 | | | --------| 階 |------| 階 |-----------| | input | 段 | | 段 | |
| \ / | |--------------------------------------

當采用傳統模式註冊事件處理函數時,實際使用的是事件冒泡處理方式。

所以如果采用傳統模式註冊事件,則點擊input元素時,先執行綁定在input上的click事件,再執行綁定在div上的click事件;

如果使用W3C標準,則根據第三個參數決定:true:捕獲階段處理,先執行div上的事件,再執行input上的事件;false則順序相反。

在低版本 IE 瀏覽器下,只支持事件冒泡模式。

三、阻止事件傳播

在使用傳統模式和W3C標準時,事件處理函數默認接受一個事件對象參數。

如果不想讓註冊在父元素上的事件被子元素捕獲,或者子元素的事件不想冒泡到父元素,則可以調用事件對象的 stopPropagation 方法。

比如 input 是 div 的子元素,所以當點擊 input 時,會同時觸發註冊在 div 和 input 上的事件。

如果只想觸發註冊在 div 上的元素,則應在捕獲階段處理 div 上的事件,並在 div 的事件處理函數中調用 event 的 stopPropagation 方法,阻止事件向子元素傳播。

如果希望點擊 input 時只觸發 input 的 click 事件,則應在冒泡階段處理 div 上的事件,並在 input 的事件處理函數中調用 event 的 stopPropagation 方法,阻止事件冒泡到父元素。

參考鏈接:Early event handlers,Traditional event registration model,Advanced event registration models,Event order。

事件捕獲與事件冒泡