JavaScript Event學習第八章:事件的順序

來源:互聯網
上載者:User

在第一章中我提到一個初次看起來可能不是那麼好理解的是一個問題:“如果一個元素和他的父元素對於同樣的事件都有事件處理常式,那麼哪個會首先執行呢?”毫無疑問,看是什麼瀏覽器。

基本問題很簡單。假設你的一個元素包含在另外一個元素中。

1 -----------------------------------<BR>| element1                        |<BR>|   -------------------------     |<BR>|   |element2                    |     |<BR>|   -------------------------     |<BR>|                                     |<BR>-----------------------------------<BR>

 這兩個元素都有onclick事件處理常式。如果使用者在element2上面單擊那麼在元素2和元素1上都觸發了單擊事件。但是哪個事件先發生呢?哪個事件處理常式會先執行呢?換句話說,事件順序(event order)是什麼呢?

 兩種模式
毫無疑問的,Netscape和微軟在過去那段很糟糕的日子裡都做出了自己的決定。
Netscape說element1先發生的。這叫事件捕獲(event capturing)。
微軟覺得element2先發生的。這叫事件冒泡(event bubbling)。
這兩種事件順序剛好相反。IE只支援事件冒泡。Mozilla,Opera 7和Konqueror兩種都支援。早一些的Opear和iCab瀏覽器兩個都不支援。

 事件捕獲
當你使用事件捕獲的時候

1 | |<BR>---------------| |-----------------<BR>| element1      | |                |<BR>|   --------- --| |-----------     |<BR>|   |element2   \ /              |     |<BR>|   -------------------------     |<BR>|        Event CAPTURING         |<BR>-----------------------------------<BR>

element1的事件處理常式會先執行,element2後執行。

  事件冒泡
但你使用事件冒泡的時候

1 / \<BR>---------------| |-----------------<BR>| element1      | |                |<BR>|   ---------- -| |-----------     |<BR>|   |element2  | |               |     |<BR>|   -------------------------     |<BR>|        Event BUBBLING           |<BR>-----------------------------------<BR>

element2的事件處理常式會先執行,element1的事件處理常式後執行。

W3C模式
W3C決定在這場戰爭中保持重力。在W3C事件模型中任何事件發生都是首先被捕獲直到到達目標元素,然後再冒泡。

1 | |  / \<BR>-----------------| |--| |-----------------<BR>| element1         | |  | |                     |<BR>|   ----------- --| |--| |-----------     |<BR>|   |element2     \ /  | |               |     |<BR>|   --------------------------------     |<BR>|       
W3C event model                 |<BR>------------------------------------------<BR>

作為設計師的你,可以隨意選擇把事件處理常式註冊在捕獲還是冒泡階段。通過之前進階模式裡面介紹的addEventListener()方法就可以完成。如果最後一個參數是true那麼就設定成為事件捕獲,如果是false就設定為事件冒泡。

假設你這樣寫
 1 element1.addEventListener('click',doSomething2,true)<BR>element2.addEventListener('click',doSomething,false)<BR>

如果使用者在element2上單擊就會發生下面的事情:
1、click事件發生在捕獲階段。這樣看來,如果element2的任何一個父元素有onclick事件處理常式那麼都會執行。
2、事件在element1上發現了doSomething2(),那麼就會執行它。
3、事件向下傳遞直到目標本身,再沒有其他的捕獲階段程式了。事件轉而進入冒泡階段然後就會執行doSomething(),也就是element2註冊在冒泡階段的事件處理常式。
4、事件再向上傳遞再檢查是否有父元素在冒泡階段設定事件處理常式。這裡沒有,所以什麼也不會發生。

反過來:

1 element1.addEventListener('click',doSomething2,false)<BR>element2.addEventListener('click',doSomething,false)<BR>

現在如果使用者在element2上面點擊就會發生:
1、事件click發生在捕獲階段。事件會尋找element2的父元素是否有在捕獲階段註冊事件處理常式,在這裡沒有。
2、事件向下傳遞直到目標本身。然後開始冒泡階段,執行dosomething(),這個是註冊在element2冒泡階段的事件處理常式。
3、事件繼續向上傳遞然後檢查是否有父元素在冒泡階段註冊了事件處理常式。
4、事件發現了element1.然後doSomething2()就被執行了。

傳統模式下的相容性
對於那些支援W3C DOM的瀏覽器來說,傳統的事件註冊

1 element1.onclick = doSomething2;<BR>

就被看做是註冊在冒泡階段的。

事件冒泡的使用
很少有設計師意識到事件捕獲或者冒泡。在網頁製作的今天,貌似沒必要讓一個冒泡事件被一系列的事件處理常式來處理。使用者也會在單擊之後發生一系列事件而感到迷惑,通常你也想讓你的事件處理常式的代碼保持一定的獨立性。當使用者點擊一個元素,發生了一些事情,當他單擊其他元素,那麼其他再發生其他事情。

當然在將來也許會改變,最好讓模式向前相容。但是如今最實用的事件捕獲和冒泡就是預設函數的註冊。

它總是會發生
首先你需要理解的就是事件捕獲或者冒泡總是在發生的。如果你為你的整個頁面定義了一個onclick事件:

1 document.onclick = doSomething;<BR>if (document.captureEvents) document.captureEvents(Event.CLICK);<BR>

你在任意元素上的click時間都會冒泡到頁面然後出發了這個事件處理常式。只有當前面的事件處理常式明確的阻止冒泡,才不會傳遞到整個頁面。

使用
因為每個事件都會在整個文檔上停止,預設的事件處理常式就變得可能。假設你有一個這樣的頁面:

1 ------------------------------------<BR>| document                         |<BR>|   ---------------  ------------  |<BR>|   | element1     |  | element2 |  |<BR>|   ---------------  ------------  |<BR>|                                  |<BR>------------------------------------<BR><BR>element1.onclick
= doSomething;<BR>element2.onclick = doSomething;<BR>document.onclick = defaultFunction;<BR>

現在如果使用者點擊了element1或者element2那麼doSomething()就會執行。在這如果你願意也可以阻止他的傳播。如果不的話,那麼defaultFunction()就會執行。使用者在其他地方的點擊也會讓defaultFunction()執行。有時候這可能有用。

設定全域的事件處理常式在寫拖動代碼的時候就很有必要。通常一個層上的mousedow事件會選擇這個層然後對mousemove事件做出回應。雖然mousedown通常註冊在這個層上來避免一些瀏覽器的bug,但是其他的事件處理常式都必須是全域的(document-wide)。

記住瀏覽器邏輯的第一定律:什麼都會發生的,而且經常是在你做的準備最少的時候。可能發生的是使用者的滑鼠瘋狂的移動然後代碼沒有跟上導致滑鼠已經不再這個層上了。

 如果在某個層上註冊了onmousemove事件處理常式,如果這個層不再響應滑鼠的移動了,那麼肯定會讓使用者感到迷惑。 
如果某個曾上註冊了onmouseup事件處理常式,那麼程式會在使用者鬆開滑鼠的時候程式沒有捕捉到造成這個層還在隨著滑鼠移動。 
在這種情況下事件冒泡就很重要,因為全域的事件處理常式會保證執行的。

關掉它
但是通常你想關閉所有的相關的捕獲和冒泡。另外,如果你的文檔結構非常的複雜(比如一大堆複雜的表格之類)你也需要關閉冒泡來節省系統資源。要不然瀏覽器就得一個個的查看父元素是否有事件處理常式。雖然可能一個都沒有,但是尋找一樣浪費時間。

在微軟模式裡你必須講事件的cancelBubble屬性設定為true。
window.event.cancelBubble = true
在W3C模式中你必須調用stopPropagation()方法。

1 e.stopPropagation()<BR>

這會阻止這個事件的冒泡階段。阻止事件的捕獲極端基本上是不可能的。我也想知道為啥。

一個完整的跨瀏覽器的代碼如下:

1 function doSomething(e)<BR>{<BR>    if (!e) var e = window.event;<BR>    e.cancelBubble = true;<BR>    if (e.stopPropagation) e.stopPropagation();<BR>}<BR>

在不支援cancelBubble的瀏覽器裡面設定也不會有啥問題。瀏覽器會建立一個這樣的屬性。當然是沒啥用的,只是為了安全。

currentTarget
我們之前講過,一個事件包含target或者srcElement包含一個發生事件的元素的引用。在我們的例子是element2,因為使用者點擊了他。

理解在捕獲和冒泡過程中這個target是不會改變的非常重要:他一直指向element2.

但是假設我們註冊了下面的事件處理常式:

1 element1.onclick = doSomething;<BR>element2.onclick = doSomething;<BR>

如果使用者點擊element2那麼doSomething()執行了兩次。那麼你怎樣知道那個HTML元素處理著這個事件呢?target/scrElement也不能給出答案,從事件一開始就一直指向element2.

為瞭解決這個問題W3C添加了currentTarget屬性。它包含一個正在處理的事件的HTML元素的引用:就是我們想要的那個。不幸的是微軟模式沒有類似的屬性。

你也可以用this關鍵字。在這個例子裡就是指向正在處理的事件的HTML元素,就先currentTarget。

微軟模式的問題
但是當你使用微軟的事件註冊模型,this關鍵字不是只想HTML元素的。然後又沒有一個像currentTarget類似的屬性,這就意味著如果你這麼做:

1 element1.attachEvent('onclick',doSomething)<BR>element2.attachEvent('onclick',doSomething)<BR>

你就不知道那個HTML元素正在處理事件。這是微軟的事件註冊模式最嚴重的問題所以就根本不要用他,即使是那些只在IE/win下的程式。

我希望微軟能夠儘快添加一個類似currentTarget的屬性或者遵循標準?我們設計急需啊。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.