JavaScript高階程式設計筆記 事件冒泡和事件捕獲

發表於2015-08-11

1、事件冒泡


要理解事件冒泡,就得先知道事件流。事件流描述的是從頁面接收事件的順序,比如如下的程式碼:

如果在body和div內都註冊了click的事件監聽,之後又點選了div區域,是body先響應還是div先響應?有意思的是,當時的瀏覽器開發團隊IE和Netscape提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape提出的事件流是事件捕獲流。

IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點(文件)。如上程式碼,點選click事件會這樣傳播:div->body->html->document(雖然我沒寫html元素,但是頁面上預設還是會存在的)

現代的所有瀏覽器都支援事件冒泡,但還是有些細微差別。IE5.5以及更早版本中的事件冒泡會跳過<html>元素(從body直接跳到document)。IE9、ff、chrome和safari則將事件一直冒泡到window物件。

2、事件捕獲


Netscape團隊則提出另一種事件流-事件捕獲。事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件,如果仍以上面的程式碼舉例:document->html->body->div。

雖然事件捕獲是Netscape唯一支援的事件流模型,但是IE9、Safari、chrome、opera和ff目前也都支援這種事件流模型。儘管“DOM2級事件”規範要求事件應該從document物件開始傳播,但這些瀏覽器都是從window物件開始捕獲事件的。

因為老版本的瀏覽器不支援事件捕獲,所以我們建議使用事件冒泡。

3、DOM事件流


“DOM2級事件”規定事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。還是上面的程式碼作為例子,單擊div元素會按照如下順序觸發事件:document->html->body->div->body->html->document。

在DOM事件流中,實際的目標(div)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件到body就停止了,下一個階段是“處於目標”階段,於是事件在div上發生,並在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播迴文件。但是多數支援DOM事件流的瀏覽器都實現了一種特定的行為:即使“DOM2級事件”規範明確要求捕獲階段不會涉及目標事件,但IE9、safari、chrome、ff和opera9.5及更高版本都會在捕獲階段觸發事件物件上的事件,結果就是有兩個機會在目標物件上面操作。(IE9、opera、ff、chrome和Safari都支援DOM事件流,IE8及更早版本不支援DOM事件流)。

4、事件處理程式


響應某個事件的函式就叫做事件處理程式

DOM0級的事件處理程式很簡單,onclick就是常用的DOM0級事件處理函式,只會在冒泡階段被處理。

而“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程式的操作:addEventListener()removeEventListener(),所有DOM節點都包含這兩個方法,並且它們都接受3個引數:要處理的事件名、作為事件處理程式的函式和一個布林值。最後這個布林值引數如果是true,表示在捕獲階段呼叫事件處理程式;如果是false,表示在冒泡階段呼叫。DOM2級方法新增事件處理程式的好處是可以新增多個事件處理程式,會按照新增順序被處理(無論是捕獲還是冒泡)。這也是為什麼DOM0級事件相容各種瀏覽器,我們卻還是要使用DOM2的原因之一。

而IE與DOM不同,它有自己的方法:attachEvent()detachEvent(),這兩個方法接受相同的兩個引數:事件處理程式名稱和事件處理程式函式。由於IE8以及更早版本只支援事件冒泡,所以通過attachEvent()新增的事件處理程式都會被新增到冒泡階段(所以不需要第三個引數)。

注意第一個引數是onclick,而非DOM標準的click。在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程式的作用域,在使用DOM0級方法的情況下,事件處理程式會在其所屬元素的作用域內執行,而在使用attachEvent()方法的情況下,事件處理程式在全域性作用域中執行,因此this等於window(這點要特別注意!!!)。attachEvent()也能新增多個事件處理程式,但是事件的執行順序和新增順序相反

5、跨瀏覽器的事件處理程式


因為瀏覽器之間的差異(其實就是IE大家都懂的),所以需要編寫跨瀏覽器的事件處理程式。

6、事件物件


在觸發DOM上的某個事件時,會產生一個事件物件event,這個物件包含著所有與事件有關的資訊。坑爹的是DOM中的事件物件和IE又有不同的玩法。

先來說說DOM中的:

上面程式碼我們應該都不陌生,分別實現了DOM0級和DOM2級的事件物件。

e有很多的屬性和方法,這裡提幾個常用的。targetcurrentTarget,target指的是事件的真正目標,而currentTarget指的是當前的目標,正是利用target我們可以做事件代理

要阻止特定事件的預設行為,我們可以使用preventDefault()方法,例如連結的預設行為就是在被單擊時會導航到其href指定的url,如果你想阻止這個預設行為,那麼通過連結的onclick事件處理程式可以取消它:

只有cancelable屬性設定為true的事件,才可以使用preventDefault()來取消其預設行為。

另外,stopPropagation()方法用於立即停止事件在DOM層中的傳播,即取消進一步的事件捕獲或冒泡。

而IE中的事件物件是這麼用的:

IE中的event物件也有很多屬性和方法,比如srcElement就是和DOM中的target屬性相同,而returnValue屬性相當於DOM中的preventDefault()方法,它們的作用都是取消給定事件的預設行為。只要將該值設定為false,就可以阻止預設行為。相應地,canceBubble屬性和DOM中的stopPropagation()方法作用相同,因為IE只支援冒泡,所以它只能取消事件冒泡。

跨瀏覽器的事件物件:

7、事件委託


有了以上作為基礎,事件委託應該是很簡單了。什麼是事件委託?對“事件處理程式過多”問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程式,就可以管理某一型別的所有事件。例如,click事件會冒泡到document層次,也就是說,我們可以為整個頁面指定一個onclick事件處理程式,而不必給每個可單擊的元素分別新增事件處理程式。

舉個經常舉的例子,比如有如下程式碼:

需要的效果是每點選相應的<li>選項,alert它裡面的單詞,或許很簡單:

但是如上程式碼繫結了三個事件,我們知道每個事件繫結都需要佔用一定的記憶體,更糟糕的是,如果在程式碼執行過程中,動態地又新增了一個li,這時它沒有繫結click的事件,我們還需要手動新增!這時,我們就可以用到事件委託技術:

好吧,就是這麼簡單!

相關文章