1. 程式人生 > >js的事件處理階段。

js的事件處理階段。

 原文:Event order 翻譯:hh54188的部落格
  前言:雖然精通jquery,但對它的原型javascript卻不是很瞭解,最近在學習javascript中遇到了一些困難,比如冒泡和捕獲,很多次被提到,但又不知究竟應用在何處。找到了一些好文章解惑,在這裡分享給大家。
  quirksmode的一系列文章都不錯,通俗易懂,這篇只是一系列中的某一篇,有機會把javascript這系列都翻譯給大家。
  原文地址在這裡http://www.quirksmode.org/js/events_order.html,句子中有標註“(?)”表示我對這個句子不是很理解,可能有誤。正式開始:
  事件的發生順序

  這個問題的起源非常簡單,假設你在一個元素中又嵌套了另一個元素

element1

| |

:並且兩者都有一個onClick事件處理函式(event handler)。如果使用者單擊元素2,則元素1和元素2的單擊事件都會被觸發。但是哪一個事件先被觸發?哪一個事件處理函式會被首先執行?換句話說,事件的發生順序到底如何?
  兩種模型
  不出所料,在那些“不堪回首”(瀏覽器大戰)的日子裡,Netscape和微軟有兩種截然不同的處理方法:
Netscape主張元素1的事件首先發生,這種事件發生順序被稱為捕獲型
微軟則保持元素2具有優先權,這種事件順序被稱為冒泡型
  這兩種事件順序是截然相反的。Explorer瀏覽器只支援冒泡事件,Mozilla,Opera7和Konqueror兩者都支援。而更古老的opera和iCab兩者都不支援
  捕獲型事件
  當你使用捕獲型事件時
| |
—————| |—————–

element1
element2 \ /

| Event CAPTURING |

:元素1的事件處理函式首先被觸發,元素2的事件處理函式最後被觸發
  冒泡型事件
  當你使用冒泡型事件時
/ \
—————| |—————–

element1
element2

| Event BUBBLING |

:元素2 的處理函式首先被觸發,元素1其次
  W3C 模型
  W3c明智的在這場爭鬥中選擇了一個擇中的方案。任何發生在w3c事件模型中的事件,首是進入捕獲階段,直到達到目標元素,再進入冒泡階段
| | / \
—————–| |–| |—————–

element1
element2 \ /

| W3C event model |

  為一個web開發者,你可以選擇是在捕獲階段還是冒泡階段繫結事件處理函式,這是通過addEventListener()方法實現的,如果這個函式的最後一個引數是true,則在捕獲階段繫結函式,反之false,在冒泡階段繫結函式。
  假設你要做
1
2
3
element1.addEventListener(‘click’,doSomething2,true)

element2.addEventListener(‘click’,doSomething,false)
  如果使用者單擊元素2,則接下來會發生:
(事件在這裡就像一個觀光客,由外至內遊覽,逐漸接近被觸發的主要元素,然後又反向離開)
單擊事件首先進入捕獲階段開始(逐漸接近元素2的方向)。檢視元素2的祖先元素中是否有在捕獲階段有onclick處理函式的
發現元素1有一個,於是doSomething2被執行
事件檢查到目標自己(元素2),捕獲階段沒有發現更多的處理函數了。事件開始進入冒泡階段,想當然執行doSomething(),這個綁定於元素2冒泡階段的函式。
事件向遠離元素2的方向,檢視是否有任何祖先元素在冒泡階段綁定了一個處理函式。沒有這樣的情況,所以什麼也沒有發生
  相反的情況是:
1
2
3
element1.addEventListener(‘click’,doSomething2,false)

element2.addEventListener(‘click’,doSomething,false)
  現在如果使用者點選元素2會發生:
單擊事件進入捕獲階段。檢視元素2的祖先元素中是否有在捕獲階段有onclick處理函式的,結果一無所獲
事件檢查到目標自己。事件開始進入冒泡階段,並且執行綁定於元素2冒泡階段的函式。doSomething()
事件開始遠離目標,檢查元素2的祖先元素中是否有在冒泡階段綁定了處理函式的
發現了一個,於是元素1的doSomething2()被執行

  相容性和傳統模式
在支援w3c dom(文件物件模型) 的瀏覽器中,傳統的事件繫結方法是
1
element1.onclick = doSomething2;
預設被視為在綁定於冒泡階段
  使用冒泡型事件
  很少的開發人員會有意識的去使用冒泡型事件或者捕獲型事件。在他們今天製作的網頁中,沒有必要讓一個事件因為冒泡而被好幾個函式處理。但是有時使用者 通常會很疑惑,因為在他們只點擊了一次滑鼠之後出現了許多種情況(多個函式被執行,因為冒泡)。而大多數情況下你還是希望你的處理函式相互獨立的。當用戶 點選了某一個元素,發生什麼,點選另一個元素,又對應發生些什麼,相互獨立,而不因為冒泡連鎖。同意樓上的return false。DOM事件處理有三個階段:

捕捉階段(capture phase):從最上層元素,直到最下層(你點選的那個target)元素。路過的所有節點都可以捕捉到該事件。
命中階段(target phase):如果該事件成功到達了target元素,它會進行事件處理。
冒泡階段(bubbling phase):事件從最下層向上傳遞,依次觸發父元素的該事件處理函式。
在任意一個階段的事件處理函式中,都可以通過呼叫event.stopPropagation來中斷事件流,後續的階段將不會被呼叫。

  一直在發生
  首先你要明白的是事件捕獲或者冒泡一直在發生。如果你給整個頁面文件的定義一個通用onclick處理函式
1
2
3
4
5
document.onclick = doSomething;

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

//第二句話我也不知道什麼意思,初學者,希望有能人能解釋
  在頁面上單擊任何元素的單擊事件,最終會冒泡至頁面最高文件層,因此觸發那個通用的處理函式,除非之前一個處理函式明確的指出終止冒泡,這樣才冒泡才不會傳播到整個文件層面
  用法(這一小節翻譯的不好,因為沒有實戰,我也不是很理解,可以在留言中補充,我會更新)

  因為任何事件傳播終止於頁面文件(這個最高層),這使預設的事件處理函式變得可能,假設你有這樣一個頁面

document

| |

1
2
3
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
1
window.event.cancelBubble = true
  在w3c模型中你必須呼叫事件的stopPropagation()方法
1
e.stopPropagation()
  這會阻止所有冒泡向外傳播。而作為跨瀏覽器解決方案應該這麼作:
1
2
3
4
5
6
7
8
9
10
function doSomething(e)

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

e.cancelBubble = true;

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

}
  在支援cancelBubble屬性的瀏覽器中設定cancelBubble無傷大雅。瀏覽器會聳一聳肩然後創造一個這個屬性。當然這也並不能真正的取消冒泡,但至少能保證這條命令是安全正確的
currentTarget
  像我們之前看到的一樣,一個事件用target或者是srcElement屬性用來表示事件究竟發生在哪個目標元素上(即使用者最初點選的元素)。在我們的例子中是元素2,因為我們單擊了它。
  非常重要的是,要明白在捕獲或者冒泡階段的目標元素是不變的,它始終與元素2相關聯。
  但是假設我們綁定了以下函式
1
2
3
element1.onclick = doSomething;

element2.onclick = doSomething;
  如果使用者單擊元素2, doSomething()會被執行兩次。但是你怎麼知道哪個html元素正在響應這個事件?target/srcElement也沒有給出線索,但人們總是更傾向於元素2,因為它是引起事件的原因(因為使用者點選的是它)。
  為了解決這個問題,w3c 增加了currentTarget這個屬性,它就指向正在處理事件的元素:這恰是我們需要的。很不幸的是微軟模型中並沒有相似的屬性
  你也可以使用”this”關鍵字。在上面的例子中,它相當於正在處理事件的html元素,就像currentTarget。
  微軟模型的問題
  但是當你使用微軟事件繫結模型時,this關鍵字並不相當於HTML元素。聯想缺少類似currentTarget屬性的微軟模型(?)——按上面的程式碼操作的話,你這麼做便意味著:
1
2
3
element1.attachEvent(‘onclick’,doSomething)

element2.attachEvent(‘onclick’,doSomething)
  你無法確切知道哪一個HTML元素正在負責處理事件,這是微軟事件繫結模型最嚴重的問題,對我來說,這也是我從不使用它的原因,哪怕是在開發僅供Windows下的IE的應用程式
  我希望能夠儘快增加currentTarget類似的屬性——或者遵循標準?web開發者們需要這些資訊
  後記:
  因為沒有實戰過javascript,所以這篇文章有些地方我並不是很理解,只能硬生的翻譯出來,比如談拖拽效果的那一段,如果大家有什麼補充和疑問可以留言給我,謝謝支援啦!