17.2. DOM2中的高級事件處理(Advanced Event Handling with DOM Level 2)
??????? 迄今為止,在本章中出現的事件處理技術都是DOM0級的一部分,所有支持JavaScript的瀏覽器都支持DOM0的API.DOM2定義了高級的事件處理API,和DOM0的API相比,有著令人矚目的不同(而且功能更強大).雖然DOM2標準并沒有把已有的API收入其中,但是DOM0級API也沒有被去除.對于基本的事件處理任務,你會覺得使用這些簡單的API更自由一些.
??????? DOM2事件模型被除了IE以外的所有瀏覽器支持.
17.2.1. 事件傳播(Event Propagation)
??????? 在DOM0級事件模型中,瀏覽器分派事件給發生事件的文檔元素.如果那個對象有相應的事件處理程序,那么就運行該程序.再沒有更多的事情發生了.情況在DOM2中就是復雜得多.在DOM2高級事件模型中,當一個文檔元素(被叫做事件的目標(target)對象)觸發了一個事件,這個目標對象的事件處理程序被觸發,除此之外,該目標對象的每一個祖輩元素都有一個或者兩個機會去處理該事件.事件傳播的過程包括三個階段.
??????? 首先,在捕獲階段(capturing phase),事件是從文檔對象(Document object)開始,沿著文檔樹向下一直到目標對象傳播的.如果任何目標對象的祖輩(不包括目標對象本身)也有一些指定注冊的捕獲事件的處理程序,在事件傳播的這個階段(捕獲階段)將運行它們.(一會兒你就會看到如何注冊正常事件處理程序和捕獲事件處理程序.)
??????? 事件傳播的下一個階段發生在目標對象自身:所有注冊到目標對象的對應事件處理程序都被運行.這和DOM0提供的事件模型是相似的.
??????? 事件傳播的第三階段是冒泡階段,或者說按文檔層次倒序的,從目標元素到文檔對象(Document object).盡管所有的事件都受事件傳播的捕獲階段(capturing phase)的影響,但是,并不是所有類型的事件都冒泡:例如,除了被定義了提交事件(submit)的form以外,把這個事件向上傳播到文檔元素是沒有任何意義的.另一方面,像mousedown這樣的一般事件對文檔中的其它元素是有意義的,所以這些事件才沿著文檔層次向上冒泡,并且觸發目標元素的祖輩元素的相應事件的處理程序.通常情況下,原始的輸入事件冒泡,而高級的語義事件不會.(稍候在本章中出現的表17-3是一個權威的列表,它指出哪些事件是冒泡的,哪些不是.)
??????? 在事件傳播期間,每個事件的處理程序都可以阻止事件的進一步傳播,只需通過調用表現這個事件的事件對象的stopPropagation( )方法就可以.事件對象和stopPropagation( )方法一會兒再進一步討論.
??????? 一些事件會使瀏覽器執行一個與事件相關聯的默認行為.例如,當點擊一個鏈接(<a> tag)的時候,瀏覽器的默認行為是轉向超鏈接.像這樣的默認行為,只有事件傳播的三個階段都完成了才會執行,在事件傳播過程中調用的任何處理程序都能阻止默認行為的發生,調用事件對象的preventDefault( )方法就可以了.
??????? 盡管這種事件傳播機制似乎讓人難以理解,但是它有助于你集中你的事件處理代碼.DOM1指出了所有的文檔元素,還指出了在那些元素上允許發生的事件(如mouseover事件).這就意味著,與舊的DOM0事件模型相比,DOM1是在很多很多的地方注冊事件處理程序.假設你想在鼠標經過每一個文檔中的段落元素(<P>)時觸發一個事件處理程序.除了在所有的段落標簽(<p>tag)注冊一個onmouseover事件處理程序之外,取而代之的辦法是為文檔對象(Document object)注冊一個單獨的事件處理程序,然后,或者在捕獲階段,或者在冒泡階段,處理這些事件.
??????? 事件傳播還有一個很重要的細節.在DOM0模型中,你只能為一個特定的對象的一個特定類型的事件注冊一個處理程序.而在DOM2模型中,你可以為一個特定對象的一個特定類型事件注冊任意數量的事件處理程序.這也適用于事件傳播時,在捕獲階段或者冒泡階段,事件對象的祖輩的處理函數被調用的情況.
17.2.2. 事件處理程序的注冊(Event Handler Registration)
??????? 在DOM0的API中,你通過在HTML中設置屬性(attribute)或者在JavaScript中設置一個對象的屬性(property)的方法注冊事件.而在DOM2模型中,你通過調用那個對象的addEventListener( )方法注冊事件處理程序.(DOM標準在這個API里使用了術語listener,但是在本文中,我將繼續使用這個術語的同義詞:handler.)這個方法有三個參數.第一個是被注冊的事件類型.事件類型是一個字符串,包含小寫的,去掉開頭的"on"的HTML中的事件屬性名.如果你在DOM0里使用HTML屬性onmousedown,你在DOM2中就是使用字符串"mousedown".
??????? 第二個參數是指定類型的事件觸發的時候應該調用的監聽函數.在你的函數被調用的時候,傳入了唯一的一個參數:Event對象.這個對象包含了事件的細節信息(如:哪個鼠標鍵被按下)和一些方法,如:stopPropagation( ).在本章后面將深入討論事件接口和它的子接口.
??????? addEventListener( )函數的最后一個參數是一個布爾值.如果為true,指定的事件處理程序在事件傳播的捕獲階段將捕獲事件.如果是false,事件處理程序就是一個正常的事件處理程序了,只有在事件直接發生在該對象上或者發生在子代對象上向上冒泡到達這個元素時,處理程序才被調用.
舉個例子,你可以像下面這樣使用addEventListener( )方法給一個form元素注冊一個提交事件(submit):
document.myform.addEventListener("submit",
???????????????????????????????? function(e) {return validate(e.target); }
???????????????????????????????? false);
如果你想捕獲發生在一個特定名字的div中的鼠標按下(mousedown)事件,你可以這樣使用addEventListener( ):
var mydiv = document.getElementById("mydiv");
mydiv.addEventListener("mousedown", handleMouseDown, true);
注意,這些例子假設你已經在你的JavaScript代碼中定義了函數名為validate( )和handleMouseDown( )的函數.
用addEventListener( )函數注冊的事件監聽程序運行在它們被定義的作用域.它們并不是在參數的作用域鏈中被調用的.
??????? 因為在DOM2中通過調用一個方法來給對象添加事件監聽器,而不是通過設置HTML屬性或者JavaScript屬性的方法,所以,你可以給一個指定對象的一個特定事件注冊多于一個的事件監聽程序.如果你通過調用addEventListener( )函數為同一對象的同一事件注冊多個監聽程序,當那個對象上那個類型的事件發生的時候(或者是向上冒泡,或者是捕獲的),所有的處理程序都被調用.重點理解一下:DOM標準并沒有保證一個對象的所有監聽函數被調用的順序,因此,你不應該依賴于函數按照被注冊的順序被執行(事實上是根本不按順序執行).還要注意的是,如果你多次注冊相同的監聽程序給同一個元素,只有第一次注冊的有效,其余的被忽略.
??????? 為什么你想要在同一個對象的同一個事件上注冊多個事件監聽程序呢?因為這有助于將你的軟件模塊化.假設,你寫了一個可重用的JavaScript代碼模塊,它使用圖像上的mouseover事件執行圖片輪換.現在再假設你有另一個模塊也想使用mouseover事件來顯示一些在HTML彈出窗體或者工具提示(Tool tip)上的附加信息.在DOM0的API中,你不得不把這兩段代碼合并成一個,這樣才能共用一個圖片對象的onmouseover屬性.另一方面,在DOM2的API中,每一個模塊都可以注冊它需要的事件監聽程序,而不必管其它的模塊.
??????? removeEventListener( )和addEventListener( )是一對方法,它需要與addEventListener( )同樣的三個參數,但它的功能是從一個對象刪除一個事件監聽函數,而不是添加.它常用于臨時注冊一個事件監聽函數,然后很快就刪除這個函數.例如,你得到一個mousedown事件的時候,想為mousemove和mouseup事件注冊臨時的捕獲事件監聽函數,這樣就可以知道,是否用戶拖拽了鼠標.然后在mouseup事件發生的時候,解除這個注冊的監聽程序.在這種情況下,事件監聽器的刪除代碼如下:
document.removeEventListener("mousemove", handleMouseMove, true);
document.removeEventListener("mouseup", handleMouseUp, true);
??????? addEventListener( )方法和removeEventListener( )方法都被定義在事件目標接口中(the EventTarget interface),在支持DOM2事件模型的Web瀏覽器中,元素和文檔節點實現了這個接口,并且提供了這些事件注冊方法.
??????? [*] 從技術上來講,DOM指出在文檔(document)中的所有節點(包括文本節點:Text nodes)都實現了這個事件對象接口.然后事實上,web瀏覽器僅在元素(Element)和文檔節點(Document nodes)上支持事件監聽器的注冊,還有窗口(Window)對象,盡管這已經超出了DOM的范圍.
17.2.3. addEventListener( )和this關鍵字(addEventListener( ) and the this Keyword)
??????? 在原來的DOM0級事件模型中,當一個函數被注冊給一個文檔元素的某個事件監聽程序時,它變成了那個文檔元素的一個方法.當這個事件監聽程序被調用時,它作為這個元素的一個方法被調用,在函數的內部,this關鍵字引用當前發生事件的元素.
??????? DOM2是用一種與語言無關的方法寫的,它指出監聽器(listeners)是對象,而不是簡單的函數.綁定了DOM的JavaScript用Javascript函數事件監聽器取代對JavaScript對象使用的需求.( The JavaScript binding of the DOM makes JavaScript functions event handlers instead of requiring the use of a JavaScript object.)不幸的是,這個綁定關系并沒有實際的指出監聽函數如何被調用,也沒有指出this關鍵字的值.
??????? 且不去考慮標準的不足,所有已知的實現都調用用addEventListener( )方法注冊的處理程序,就像這些處理程序是目標對象的方法一樣.也就是說,當監聽程序被調用的時候,this關鍵字引用這個監聽程序被注冊的那個對象.如果你寧愿不依賴這種未指定的行為,你可以使用傳入監聽程序的事件對象(Event object)的currentTarget屬性.在本章稍候的討論中你會看到,currentTarget屬性引用事件監聽程序被注冊的對象.
17.2.4. 把對象(Objects)注冊為事件監聽器(Registering Objects as Event Handlers)
??????? addEventListener( )允許你注冊一個事件監聽函數.對于面向對象編程,你可能更喜歡定義一個客戶端對象的方法作為事件監聽程序,然后把它們作為那個對象的方法進行調用.對于Java程序員,DOM標準允許這樣做:事件監聽程序可以是實現了EvnentListener接口并且有一個名為handleEvent()的方法的對象.在Java中,當你注冊一個事件監聽程序時,你給addEventListener( )傳入一個對象,而不是一個函數.簡單的說,綁定了DOM API的JavaScript不需要你去實現EventListener接口,相反的,允許你直接給addEventListener( )傳遞一個函數引用.
然而,如果你在寫一個面向對象的JavaScript程序,并且更喜歡用對象作為事件監聽程序,你可以用一個像下邊這樣的函數來注冊:
function registerObjectEventHandler(element, eventtype, listener, captures) {
??? element.addEventListener(eventtype,
???????????????????????????? function(event) { listener.handleEvent(event); }
???????????????????????????? captures);
}
??????? 只要一個對象定義了handleEvent( )方法,就可以用這個函數把該對象注冊為一個事件監聽程序.那個方法作為監聽對象的方法被調用,this關鍵字引用這個監聽對象,而不是產生事件的文檔元素.
??????? 盡管這不是DOM標準的一部分,Firefox(和其它基于Mozilla codebase的瀏覽器)允許把定義了handleEvent()方法的事件監聽對象直接傳遞給addEventListener()方法,來代替函數.對于這些瀏覽器,就沒有必要定義一個像剛才展示的注冊函數了.
17.2.5. 事件模型和事件類型(Event Modules and Event Types)
??????? 如我前面所說,DOM2是模塊化的,所以,一個實現可以支持其中的一部分而忽略其它對其它部分的支持.事件API(Events API)就是這樣一個模塊.你可以像這樣來測試一個瀏覽器是否支持這個模塊:
document.implementation.hasFeature("Events", "2.0")
??????? 然而,事件模塊只包含用于基本事件監聽結構的API.子模塊提供對特定類型事件的支持.每個子模塊都提供對一類相關事件類型的支持,并且定義了傳入事件監聽程序的事件類型.例如,名為MouseEvents的子模塊提供了mousedown, mouseup, click等相關事件的類型.它也定義了MouseEvent接口.實現了那個接口的對象,為任何一個被這個模塊支持的事件類型,被傳入事件監聽程序.
??????? 表17-2列出了每一個事件模塊,它定義的接口,和被它支持的事件類型.注意,DOM2并沒有把任何鍵盤事件標準化,因此這里沒有列出鍵盤事件模塊.然而,當前的瀏覽器都支持鍵盤事件,在本章的后面,你會了解的更多一些.(此處省略幾句和MutationEvents模塊相關的描述)
Table 17-2. Event modules, interfaces, and types
Module name
|
Event interface
|
Event types
|
HTMLEvents
|
Event
|
abort, blur, change, error, focus, load, reset, resize, scroll, select, submit, unload
|
MouseEvents
|
MouseEvent
|
click, mousedown, mousemove, mouseout, mouseover, mouseup
|
UIEvents
|
UIEvent
|
DOMActivate, DOMFocusIn, DOMFocusOut
|
??????? 如你在表17-2中所見,HTMLEvents和MouseEvents模塊定義的事件類型和DOM0的事件模塊是非常相似的.UIEvents模塊定義了事件類型,這和被HTML表單元素支持的focus,blur和click事件很相似,但更通用的,所以,他們能被任何可以接受焦點或者被激活的文檔元素產生.
??????? 如前所述,當一個事件發生的時候,它的監聽程序被傳入一個實現了那個類型事件的事件接口對象.這個對象的屬性提供了對事件監聽程序可能有用的細節信息.表17-3再一次列出標準的事件,但這次是按事件類型組織的,而不是事件模型.對于每個事件類型,該表都指出傳入它的監聽程序的事件對象的種類,是否這個事件有一個可以用preventDefault()方法阻止發生的默認行為.對于HTMLEvents模塊中的事件,表格中的第五列指出哪些HTML元素可以產生該事件.對于所有其它的事件類型,第五列指出事件對象的哪些屬性包含了有意義的事件細節信息.注意,在這一列中列出的屬性,不包括被基本事件接口定義的對所有事件類型都有意義的屬性.
Table 17-3. Event types
Event type
|
Interface
|
B
|
C
|
Supported by/detail properties
|
abort
|
Event
|
yes
|
no
|
<img>, <object>
|
blur
|
Event
|
no
|
no
|
<a>, <area>, <button>, <input>, <label>, <select>, <textarea>
|
change
|
Event
|
yes
|
no
|
<input>, <select>, <textarea>
|
click
|
MouseEvent
|
yes
|
yes
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail
|
error
|
Event
|
yes
|
no
|
<body>, <frameset>, <img>, <object>
|
focus
|
Event
|
no
|
no
|
<a>, <area>, <button>, <input>, <label>, <select>, <textarea>
|
load
|
Event
|
no
|
no
|
<body>, <frameset>, <iframe>, <img>, <object>
|
mousedown
|
MouseEvent
|
yes
|
yes
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail
|
mousemove
|
MouseEvent
|
yes
|
no
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey
|
mouseout
|
MouseEvent
|
yes
|
yes
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, relatedTarget
|
mouseover
|
MouseEvent
|
yes
|
yes
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, relatedTarget
|
mouseup
|
MouseEvent
|
yes
|
yes
|
screenX, screenY, clientX, clientY, altKey, ctrlKey, shiftKey, metaKey, button, detail
|
reset
|
Event
|
yes
|
no
|
<form>
|
resize
|
Event
|
yes
|
no
|
<body>, <frameset>, <iframe>
|
scroll
|
Event
|
yes
|
no
|
<body>
|
select
|
Event
|
yes
|
no
|
<input>, <textarea>
|
submit
|
Event
|
yes
|
yes
|
<form>
|
unload
|
Event
|
no
|
no
|
<body>, <frameset>
|
DOMActivate
|
UIEvent
|
yes
|
yes
|
detail
|
DOMFocusIn
|
UIEvent
|
yes
|
no
|
none
|
DOMFocusOut
|
UIEvent
|
yes
|
no
|
none
|
??????? 被DOM0和DOM2支持的事件類型大體相同(UIEvents除外).DOM2標準添加了對abort,error,resize和scroll事件類型的支持,這些不是HTML 4的標準,但它不支持HTML 4標準中的鍵盤事件和雙擊事件.(取而代之的是,傳入click事件處理程序的對象的細節屬性指出了發生的連續點擊的次數.)
17.2.6. 事件接口和事件細節(Event Interfaces and Event Details)
??????? 一個事件發生的時候,DOM2提供了關于這個事件的其它細節(例如何時何地發生的),這些信息作為傳入到事件監聽程序的對象的屬性出現.每一個事件模塊都有一個相關聯的事件接口,該接口指出和那個類型事件相關的細節.表17-2列出了三種不同的事件模塊和三種不同的事件接口.
??????? 事實上這三個接口是互相關聯的,并且形成了一個層次關系.事件接口是這個層次的根層;所有的事件對象都實現了它大部分基本的事件接口.UIEvent是事件接口的子接口:任何實現了UIEvent接口的事件對象都實現了Event接口的屬性和方法.MouseEvent接口又是UIEvent的子接口.舉個例子,這就是說,傳給點擊事件處理程序的事件對象實現了在MouseEvent, UIEvent, 和Event接口中定義的所有屬性和方法.
接下來的部分介紹的是事件接口,并且著重講最重要的屬性和方法.
17.2.6.1. 事件(Event)
??????? 在HTMLEvents模塊中定義的事件類型使用Event接口.所有其它的事件類型都使用Event的子接口,子接口被所有的事件對象實現,并且提供了應用于那個事件類型的細節信息.事件接口定義了如下屬性(注意,這些屬性和所有事件子接口的屬性都是只讀的):
type
??????? 發生的事件類型.這個屬性的值是事件類型的名字,它和用于注冊事件處理程序時使用的字符串是一樣的(如: click 或者 mouseover).
target
??????? 事件發生的節點,可能與currentTarget不同.
currentTarget
??????? 正在處理事件的節點(也就是正在運行的事件處理程序所屬的節點).如果在事件被捕獲或者冒泡階段被處理,這個屬性的值和target屬性的值是不同的.如前所述,你可以在你的事件處理程序中用這個屬性代替this關鍵字
eventPhase
??????? 指出正在理的是事件傳播的哪一個階段的數值.該值為三個常中之一:Event.CAPTURING_PHASE, Event.AT_TARGET, 或者 Event.BUBBLING_PHASE.
timeStamp
??????? 指出該事件何時發生的日期對象
bubbles
??????? 指出該事件是否沿文檔樹向中冒泡的布爾值.
cancelable
??????? 指出該事件是否有一個可以用prevertDefault()阻止的和事件相關聯的默認行為的布爾值.
??????? 除了這七個屬性以外,事件接口定義了兩個方法,也都被事件對象實現了,它們是:stopPropagation( ) 和 preventDefault( ).任何事件處理程序都可以調用stopPropagation( )來阻止正在被傳播的事件越過正在被處理的節點.任何事件處理程序都可以調用preventDefault( )來阻止瀏覽器執行與該事件關聯的默認行為.在DOM2中調用preventDefault( ),就像在DOM0中返回false.
17.2.6.2. 用戶信息事件(UIEvent)
??????? 用戶信息事件接口是Event的子接口.它定義了被傳遞到DOMFocusIn, DOMFocusOut和DOMActivate的事件對象的類型.這些事件類型通常是用不到的;但對于UIEvent接口來講,比較重要的是它是MouseEvent的父接口.除了在Event中定義的屬性外,UIEvent還定義了兩個屬性:
view
??????? 發生的事件所在的窗口對象(Window Object:known as a view in DOM terminology).
detail
??????? 可以提供附加信息的數值.對于click,mousedown和mouseup事件,這個字段代表點擊計數:1代表單擊,2代表雙擊,3代表三擊.(注意,每次點擊都產生一個事件,但是如果多次點擊間隔足夠短,detail屬性就會指示出來.也就是說,detail為2的鼠標事件要優先于detail為1的鼠標事件.)對于DOMActivate事件,這個字段值為1代表正常激活,2代表極度活躍(hyperactivation),比如雙擊或者Shift-Enter組合鍵.
17.2.6.3. MouseEvent
MouseEvent接口繼承了Event和UIEvent的屬性和方法,還定義了如下附加屬性:
附加屬性:
button
??????? 數值類型,指出在mousedown, mouseup或者click事件期間哪一個鼠標鍵改變了狀態.0代表左鍵,1代表中鍵,2代表右鍵.只有當一個按鍵改變狀態時,才使用這個屬性;例如:不能用于報告mousemove事件發生時哪個鍵是被按下的.注意Netscape 6得到的值為1,2,3,而不是0,1,2.這個問題在Netscape 6.1中已經修正了.
altKey , ctrlKey, metaKey, shiftKey
??????? 這四個布爾值代表,當一個鼠標事件發生時,是否 Alt, Ctrl, Meta或者Shift鍵被按下.與button屬性不同,這些按鍵屬性對于任何鼠標事件都是有效的.
clientX, clientY
??????? 這兩個屬性指出鼠標指針的X和Y坐標,相對于瀏覽器窗口的客戶區.注意這個坐標并沒有計算文檔的滾動高度或者寬度在內:如果事件發生在窗口的最上邊,不管這個文檔已經向下滾動了多遠,clientY就是0.不幸的是,DOM2并沒有提供一個標準的方法去轉換這個窗口坐標為文檔坐標.在除了IE以外的瀏覽器中,你可以加上window.pageXOffset和window.pageYOffset.
screenX, screenY
??????? 這兩個屬性指出鼠標指針相對于用戶顯示器的左上角的坐標.如果你打算在鼠標事件發生的地點或者附近打開一個窗口,這兩個屬性就有用了.
relatedTarget
??????? 這個屬性引用一個相對于事件的target節點的節點.對于mouseover事件,它引用當鼠標經過target節點時鼠標離開的那個節點.對于mouseout事件,它引用當鼠標離開目標節點時,鼠標進入的節點.對于其它事件,這兩個屬性是無用的.
17.2.7. 混合事件模型(Mixing Event Models)
??????? 到現在為止,我們討論了傳統的DOM0級事件模型,和新的標準的DOM2模型.為了向后兼容,支持DOM2模型的瀏覽器將繼續支持DOM0級事件模型.這就意味著,你可以在一個文檔里混合使用這兩種事件模型.
??????? 支持DOM2級事件模型的web瀏覽器總是傳遞一個事件對象給事件監聽程序,這和用DOM0級事件模型的HTML屬性或者JavaScript屬性注冊事件處理程序是一致的,理解這一點很重要.當事件監聽程序作為一個HTML屬性被定義的時候,它被暗中轉換成一個函數,這個函數有一個名為event的參數.這就意味著,像這樣的一個事件監聽程序可以用標識符event來引用事件對象.
??????? DOM標準承認DOM0級事件模型繼續保留使用,并指出,對待DOM0事件模型的監聽程序的注冊方法就像用addEventListener( )注冊的一樣.也就是說,如果你給一個文檔元素e的onclick屬性賦值為函數f(或者說設置對應的HTML中的onclick屬性),它和下面這種注冊方法是一樣的:
e.addEventListener("click", f, false);
當函數f被調用的時候,傳入一個事件對象作為參數,盡管這個函數是用DOM0模型注冊的.
???
posted on 2006-11-16 21:19
梅雪香 閱讀(3672)
評論(7) 編輯 收藏