一、背景
假設有一個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。