【原創翻譯】生動詳細解釋javascript的冒泡和捕獲,包懂包會

前言:雖然精通jquery,但對它的原型javascript卻不是很瞭解,最近在學習javascript中遇到了一些困難,好比冒泡和捕獲,不少次被提到,但又不知究竟應用在何處。找到了一些好文章解惑,在這裏分享給你們。javascript

quirksmode的一系列文章都不錯,通俗易懂,這篇只是一系列中的某一篇,有機會把javascript這系列都翻譯給你們。html

原文地址在這裏http://www.quirksmode.org/js/events_order.html,句子中有標註「(?)」表示我對這個句子不是很理解,可能有誤。正式開始:java

事件的發生順序jquery

這個問題的起源很是簡單,假設你在一個元素中又嵌套了另外一個元素web

-----------------------------------
| element1                        |
|   -------------------------     |
|   |element2               |     |
|   -------------------------     |
|                                 |
-----------------------------------

:而且二者都有一個onClick事件處理函數(event handler)。若是用戶單擊元素2,則元素1和元素2的單擊事件都會被觸發。可是哪個事件先被觸發?哪個事件處理函數會被首先執行?換句話說,事件的發生順序到底如何?瀏覽器

 

兩種模型安全

不出所料,在那些「不堪回首」(瀏覽器大戰)的日子裏,Netscape和微軟有兩種大相徑庭的處理方法:app

  • Netscape主張元素1的事件首先發生,這種事件發生順序被稱爲捕獲型
  • 微軟則保持元素2具備優先權,這種事件順序被稱爲冒泡型

這兩種事件順序是截然相反的。Explorer瀏覽器只支持冒泡事件,Mozilla,Opera7和Konqueror二者都支持。而更古老的opera和iCab二者都不支持dom

 

捕獲型事件函數

當你使用捕獲型事件時

               | |
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  \ /          |     |
|   -------------------------     |
|        Event CAPTURING          |
-----------------------------------

:元素1的事件處理函數首先被觸發,元素2的事件處理函數最後被觸發

冒泡型事件

當你使用冒泡型事件時

               / \
---------------| |-----------------
| element1     | |                |
|   -----------| |-----------     |
|   |element2  | |          |     |
|   -------------------------     |
|        Event BUBBLING           |
-----------------------------------

:元素2 的處理函數首先被觸發,元素1其次

W3C 模型

W3c明智的在這場爭鬥中選擇了一個擇中的方案。任何發生在w3c事件模型中的事件,首是進入捕獲階段,直到達到目標元素,再進入冒泡階段

                 | |  / \
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

爲一個web開發者,你能夠選擇是在捕獲階段仍是冒泡階段綁定事件處理函數,這是經過addEventListener()方法實現的,若是這個函數的最後一個參數是true,則在捕獲階段綁定函數,反之false,在冒泡階段綁定函數。

 假設你要作

element1.addEventListener('click',doSomething2,true)

element2.addEventListener('click',doSomething,false)

若是用戶單擊元素2,則接下來會發生:

(事件在這裏就像一個觀光客,由外至內遊覽,逐漸接近被觸發的主要元素,而後又反向離開)

  1. 單擊事件首先進入捕獲階段開始(逐漸接近元素2的方向)。查看元素2的祖先元素中是否有在捕獲階段有onclick處理函數的
  2. 發現元素1有一個,因而doSomething2被執行
  3. 事件檢查到目標本身(元素2),捕獲階段沒有發現更多的處理函數了。事件開始進入冒泡階段,想固然執行doSomething(),這個綁定於元素2冒泡階段的函數。
  4. 事件向遠離元素2的方向,查看是否有任何祖先元素在冒泡階段綁定了一個處理函數。沒有這樣的狀況,因此什麼也沒有發生

相反的狀況是:

element1.addEventListener('click',doSomething2,false)

element2.addEventListener('click',doSomething,false)

如今若是用戶點擊元素2會發生:

  1. 單擊事件進入捕獲階段。查看元素2的祖先元素中是否有在捕獲階段有onclick處理函數的,結果一無所得
  2. 事件檢查到目標本身。事件開始進入冒泡階段,而且執行綁定於元素2冒泡階段的函數。doSomething()
  3. 事件開始遠離目標,檢查元素2的祖先元素中是否有在冒泡階段綁定了處理函數的
  4. 發現了一個,因而元素1的doSomething2()被執行

兼容性和傳統模式

在支持w3c dom(文檔對象模型) 的瀏覽器中,傳統的事件綁定方法是

element1.onclick = doSomething2;

默認被視爲在綁定於冒泡階段

 

使用冒泡型事件

不多的開發人員會有意識的去使用冒泡型事件或者捕獲型事件。在他們今天製做的網頁中,沒有必要讓一個事件由於冒泡而被好幾個函數處理。可是有時用戶一般會很疑惑,由於在他們只點擊了一次鼠標以後出現了許多種狀況(多個函數被執行,由於冒泡)。而大多數狀況下你仍是但願你的處理函數相互獨立的。當用戶點擊了某一個元素,發生什麼,點擊另外一個元素,又對應發生些什麼,相互獨立,而不由於冒泡連鎖。

 

一直在發生

首先你要明白的是事件捕獲或者冒泡一直在發生。若是你給整個頁面文檔的定義一個通用onclick處理函數

document.onclick = doSomething;

if (document.captureEvents) document.captureEvents(Event.CLICK);

//第二句話我也不知道什麼意思,初學者,但願有能人能解釋

在頁面上單擊任何元素的單擊事件,最終會冒泡至頁面最高文檔層,所以觸發那個通用的處理函數,除非以前一個處理函數明確的指出終止冒泡,這樣才冒泡纔不會傳播到整個文檔層面

 

用法(這一小節翻譯的很差,由於沒有實戰,我也不是很理解,能夠在留言中補充,我會更新)

由於任何事件傳播終止於頁面文檔(這個最高層),這使默認的事件處理函數變得可能,假設你有這樣一個頁面

------------------------------------
| document                         |
|   ---------------  ------------  |
|   | element1    |  | element2 |  |
|   ---------------  ------------  |
|                                  |
------------------------------------
element1.onclick = doSomething;
element2.onclick = doSomething;
document.onclick = defaultFunction;

 

如今若是用戶單擊元素1或者元素2,doSomething()將被執行。若是你願意的話,若是你不想讓事件冒泡至執行defaultFunction(),你能夠在這裏阻止事件冒泡向上傳播,。可是若是用戶點擊頁面上的其餘部位,defaultFunction()仍是會被執行。這樣的效果或許有時能用的上。

 

設置頁面­——使處理函數有範圍較大的觸發面積,在「拖拽效果」腳本中是必須的。通常來講在某一個元素層上發生 mousedown事件意味着選擇了這個元素,而且使它可以響應mousemove事件。雖然mousedown一般綁定於這個元素層上以免瀏覽器bug,可是其餘二者的事件函數的範圍必須是整個頁面(?)

 

記住瀏覽器學的第一法則(First Law of Browserology)是:一切皆有可能(anything can happen),而且是在你起碼有點準備的時候。因此有可能發生的是,用戶拖拽時,大幅度在頁面上移動他的鼠標,腳本卻不能在大幅度中作出反應,以致於鼠標也就再也不停留在元素層上了

  • 若是onmouseover處理函數綁定在元素層上,這個元素層不會再對鼠標的移動有任何反應,這會讓用戶以爲奇怪
  • 若是onmouseup處理函數綁定在元素層上,事件也不能被觸發,後果是,用戶想放下這個元素層後,元素層持續對鼠標移動作出反應。這會引發(用戶)更多的迷惑(?)

因此在這個例子中,事件冒泡很是的有用,由於將你的處理函數放在頁面層能保證他們一直能被執行

 

把它給關了

可是通常狀況下,你會想關了全部的冒泡和捕獲以保證函數之間不會打擾到對方。除此以外,若是你的文檔結構至關的複雜(許多table之間相互嵌套或者諸如此類),你也會爲了節省系統資源,而關閉冒泡。此時瀏覽器不得不檢查目標元素的每個祖先,看是否它有一個處理函數。即便一個都沒有找到,剛剛的搜索一樣花費很多時間

 

在微軟的模型中,你必須設置事件的cancelBubble的屬性爲true

window.event.cancelBubble = true

在w3c模型中你必須調用事件的stopPropagation()方法

e.stopPropagation()

這會阻止全部冒泡向外傳播。而做爲跨瀏覽器解決方案應該這麼做:

複製代碼
function doSomething(e)

{
     if (!e) var e = window.event;

e.cancelBubble = true;

if (e.stopPropagation) e.stopPropagation();

}
複製代碼

在支持cancelBubble屬性的瀏覽器中設置cancelBubble無傷大雅。瀏覽器會聳一聳肩而後創造一個這個屬性。固然這也並不能真正的取消冒泡,但至少能保證這條命令是安全正確的

 

currentTarget

像咱們以前看到的同樣,一個事件用target或者是srcElement屬性用來表示事件究竟發生在哪一個目標元素上(即用戶最初點擊的元素)。在咱們的例子中是元素2,由於咱們單擊了它。

很是重要的是,要明白在捕獲或者冒泡階段的目標元素是不變的,它始終與元素2相關聯。

可是假設咱們綁定了如下函數

element1.onclick = doSomething;

element2.onclick = doSomething;

若是用戶單擊元素2, doSomething()會被執行兩次。可是你怎麼知道哪一個html元素正在響應這個事件?target/srcElement也沒有給出線索,但人們老是更傾向於元素2,由於它是引發事件的緣由(由於用戶點擊的是它)。

爲了解決這個問題,w3c 增長了currentTarget這個屬性,它就指向正在處理事件的元素:這恰是咱們須要的。很不幸的是微軟模型中並無類似的屬性

你也可使用」this」關鍵字。在上面的例子中,它至關於正在處理事件的html元素,就像currentTarget。

 

微軟模型的問題

可是當你使用微軟事件綁定模型時,this關鍵字並不至關於HTML元素。聯想缺乏相似currentTarget屬性的微軟模型(?)——按上面的代碼操做的話,你這麼作便意味着:

element1.attachEvent('onclick',doSomething)

element2.attachEvent('onclick',doSomething)

你沒法確切知道哪個HTML元素正在負責處理事件,這是微軟事件綁定模型最嚴重的問題,對我來講,這也是我從不使用它的緣由,哪怕是在開發僅供Windows下的IE的應用程序

 

我但願可以儘快增長currentTarget相似的屬性——或者遵循標準?web開發者們須要這些信息

 後記:

由於沒有實戰過javascript,因此這篇文章有些地方我並非很理解,只能硬生的翻譯出來,好比談拖拽效果的那一段,若是你們有什麼補充和疑問能夠留言給我,謝謝支持啦!

相關文章
相關標籤/搜索