背景知識
什么是事件?
直觀的說就是網頁上發生的事情,大部分是指用戶的鼠標動作和鍵盤動作,如點擊、移動鼠標、按下某個鍵。為什么說大部分呢,因為事件不單單只有這兩部分,還有其他的例如document的load和unloaded。只不過我們更加關注的是用戶的操作。
事件模型:規范事件的定義的一種標準
事件的三種模型:
- 原始事件模型(DOM 0級事件模型)
- IE事件模型
- DOM2事件模型
DOM2事件模型:
此模型是W3C制定的標準模型,既然是標準,那大家都得按這個來,我們現在使用的現代瀏覽器(指IE6~8除外的瀏覽器)都已經遵循這個規范。W3C制定的事件模型中,“DOM2級事件”中規定的事件流同時支持了事件捕獲階段和事件冒泡階段,而作為開發者,我們可以選擇事件處理函數在哪一個階段被調用。
一次事件的發生包含三個過程:
(1)capturing phase:事件捕獲階段。
事件被從document一直向下傳播到目標元素,在這過程中依次檢查經過的節點是否注冊了該事件的監聽函數,若有則執行。
(2)target phase:事件處理階段。
事件到達目標元素,執行目標元素的事件處理函數.
(3)bubbling phase:事件冒泡階段。
事件從目標元素上升一直到達document,同樣依次檢查經過的節點是否注冊了該事件的監聽函數,有則執行。
所有的事件類型都會經歷captruing phase(事件捕獲),但是只有部分事件會經歷bubbling phase(事件冒泡)階段,例如submit事件就不會被冒泡。
1. 是什么?
<div id="outer">
<p id="inner">Click me!</p>
</div>
上面的代碼當中一個div元素當中有一個p子元素,如果兩個元素都有一個click的處理函數,那么我們怎么才能知道哪一個函數會首先被觸發呢?
為了解決這個問題微軟和網景提出了兩種幾乎完全相反的概念。
**事件流 : ** 指的是頁面中接收事件的順序.
事件冒泡
微軟提出了名為事件冒泡(event bubbling)的事件流。
事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。
當一個DOM元素上的事件被觸發的時候(如:按鈕點擊事件),這個元素的所有父元素 中,如果也綁定有該相同事件,則也會被觸發, 觸發的順序就是先從 : 當前元素的事件 ==> 臨近父元素 ==> 父元素......,這一過程被稱為事件冒泡
因此上面的例子在事件冒泡的概念下發生click事件的順序應該是:
p -> div -> body -> html -> document
IE,火狐和chrome瀏覽器都是事件冒泡.
事件捕獲
網景提出另一種事件流名為事件捕獲(event capturing)。
當一個DOM元素上的事件被觸發的時候(如:按鈕點擊事件),這個元素的所有父元素 中,如果也綁定有該相同事件,則也會被觸發, 觸發的順序就是先從 : ....... 父元素 ==> 臨近父元素 ==> 當前元素的事件,這一過程被稱為事件捕獲
與事件冒泡相反,事件會從最外層開始發生,直到最具體的元素。
上面的例子在事件捕獲的概念下發生click事件的順序應該是:
document -> html -> body -> div -> p
圖解:
2. 解決了什么問題?
這兩個概念都是為了解決頁面中事件流(事件發生順序)的問題。
http://www.imooc.com/article/9833
3. 執行原理
<ul>
<li>
<p>
<a> </div>
</p>
</li>
</ul>
事件捕獲階段:事件從最上一級標簽開始往下查找,直到捕獲到事件目標(target)。
事件冒泡階段:事件從事件目標(target)開始,往上冒泡直到頁面的最上一級標簽。
事件捕獲當你使用事件捕獲時,父級元素先觸發,子級元素后觸發,即div先觸發,p后觸發。
事件冒泡當你使用事件冒泡時,子級元素先觸發,父級元素后觸發,即p先觸發,div后觸發。
4. 如何使用?
說到事件的執行順序,那么我們需要知道一個給元素添加事件的方法, 即給某元素動態綁定事件
W3C為我們提供了addEventListener()函數用來為指定的dom元素動態綁定事件。
語法:
element.addEventListener( event , function , useCapture )
提示: 使用 removeEventListener()方法來移除 addEventListener() 方法添加的事件句柄。
function sayHello() {
console.log("hello");
}
var myDiv = document.getElementById("myDiv");
myDiv.addEventListener("click", sayHello);
這樣我們點擊id為myDiv的元素時,控制臺就會輸出"Hello"。
事件冒泡
有如下html代碼:
下面設置了四個函數用來進行事件綁定:
使用下面的代碼,我們可以獲取四個元素對應DOM
現在,我試著同時分別為grandpa和grandson綁定sleep和doingHomework事件:
這時我們點擊最外層的grandpa時,當然會觸發sleep函數,然而當我們點擊grandson時,控制臺的輸出如下:
原因:這是因為grandson在grandpa之上,當點擊grandson時,同時也在grandpa上進行了點擊操作,所以在執行了doingHomework后,還會觸發grandpa的sleep函數。
這種當滿足條件后從子元素到父元素依次觸發其上事件的處理方式叫做事件冒泡
我們也為father和child分別綁定watchTV和playingCard函數
事件冒泡()
grandpa.addEventListener("click", sleep);
grandson.addEventListener("click", doingHomework);
father.addEventListener("click", watchTV);
child.addEventListener("click", playingCard);
事件捕獲
事件捕獲與事件冒泡完全相反,先觸發祖先元素的事件,然后再逐級觸發子元素的事件。默認情況下,綁定事件時,采用事件冒泡原則,如果想要進行事件捕獲的話,需要設置一個參數 。
可以為addEventListener函數添加第三個參數useCapture,參數值是布爾值,默認是false。當useCapture為false時,事件處理采取事件冒泡的原則,當userCapture為true時,則采取事件捕獲的原則
這時,當點擊grandson時,就會先執行祖先元素的事件,再執行后代元素的事件了,控制臺的輸出如下圖所示:
雖然默認情況下,useCapture的值是false,但我推薦我們在綁定函數時把它明顯的寫出來以避免瀏覽器兼容性的問題。
事件冒泡與事件捕獲要是同時進行怎么辦
有思想的同學肯定會思考這樣一個問題,在上述綁定事件的代碼中,第三個參數不是全部設置的true,就是全部設置成false,那如果既有true,又有false,有的元素設置成按事件冒泡處理,有的元素設置成按事件捕獲處理,那怎么辦呢?
直接告訴大家答案,我們的瀏覽器更“喜愛”事件捕獲:
它會先把useCapture為false的元素綁定事件放到一邊,按照事件捕獲正常的順序執行useCapture為true的元素綁定事件,最后在按照事件冒泡順序執行useCapture為false。
現在我們作如下更改
按照上述原則,當點擊grandson時,先執行useCapture為true的元素的綁定事件,又按照事件捕獲原則,先執行grandpa的事件,再執行child的事件。之后,再按照事件捕獲順序執行useCapture為false的事件,輸出結果如下:
阻止事件冒泡和捕獲
我們可以利用時間對象event的stopPropagation()
方法阻止事件的進一步傳播。
我們修改一下doingHomework函數:
發現事件執行到doingHomework就被阻斷了,其后不會在事件傳播到父元素。
值得注意的是,event.stopPropagation()函數并不會阻止其下函數內容的執行。
*如果你使用的是jquery的事件綁定,也可以直接在函數中使用return false來阻止事件的傳播(當然event.stopPropagation同樣有效),與event.stopPropagation不同的是,return false會阻止同函數下面的代碼執行 *
傳統綁定事件方式在一個支持W3C DOM的瀏覽器中,像這樣一般的綁定事件方式,是采用的事件冒泡方式。
ele.onclick = doSomething2
IE瀏覽器
如上面所說,IE只支持事件冒泡,不支持事件捕獲,它也不支持addEventListener函數,不會用第三個參數來表示是冒泡還是捕獲,它提供了另一個函數attachEvent。
ele.attachEvent("onclick", doSomething2);
不是所有的事件都能冒泡,例如:blur、focus、load、unload。
事件冒泡的好處