DOM Level 3 Events: DOM事件架構(2-1)

李鬆峰發表於2011-10-26

摘譯自:Document Object Model (DOM) Level 3 Events Specification

3. DOM事件架構

3.1 事件分派與DOM事件流

本節定義本規範所定義的事件模型的事件分派機制。應用可以使用EventTarget.dispatchEvent()方法分派事件物件,而實現在分派事件物件時也應當如同使用了該方法一樣。這個方法的行為取決於與底層事件關聯的“事件流”。事件流描述的是事件物件通過某種資料結構傳播的過程。舉例來說,在把一個事件物件分派到XML文件中的一個元素時,該物件會經過文件的某些部分,而具體的傳播路徑是由本節末尾定義的DOM事件流決定的。

enter image description here
圖1:根據DOM事件流的定義,在DOM樹中分派的事件物件的傳播路徑示意圖

事件物件總是被分派到最近事件目標。分派開始時,實現應當先確定事件物件的傳播路徑

傳播路徑應當是由一組事件目標組成的有序列表,該列表定義了事件物件應當經過的元素。對基於樹圖的DOM實現來說,傳播路徑應當反映文件的層次化的樹形結構。列表最後一項應當是最近事件目標,而列表前面的項則稱為事件目標的祖先元素,與事件目標緊鄰的項是“事件目標的父元素”。傳播路徑一經定義,則不再改變;對基於樹圖的DOM實現而言,即使傳播路徑中的某個元素在DOM中移動了位置,甚至被從DOM中刪除了,傳播路徑也不再改變。舉例來說,在事件物件被分派的過程中,DOM事件流中的監聽器可能會改變最近事件目標在文件中的位置;這個變化並不影響傳播路徑。此外,監聽器中的異常不會阻止事件物件的傳播,也不會影響傳播路徑

下一步,事件物件應當完成一或多個事件階段。本規範定義了三個事件階段:捕獲階段、目標階段和冒泡階段。事件物件要按照下面的定義,以指定的順序沿著部分傳播路徑完成這些階段。如果某個階段沒有被支援,或者事件物件的傳播已經被停止,應當跳過相應的階段。舉例來說,如果Event.bubbles屬性被設定為false,就要跳過冒泡階段,而如果在分派事件物件之前已經呼叫了Event.stopPropagation(),那就應當跳過所有階段。

  1. 捕獲階段:事件物件應當從defaultView經過目標的祖先元素傳播到目標的父元素。這個階段也稱為“捕捉階段”(capturing phase)。註冊到這個階段的事件監聽器應當在事件物件到達其目標之前處理該事件。
  2. 目標階段:事件物件應當到達它的最近事件目標。這個階段也稱為“到達階段”(at-target phase)。註冊到這個階段的事件監聽器應當在事件物件一到達其目標就處理該事件。
  3. 冒泡階段:事件物件反向經過目標的祖先元素,從目標的父元素開始,到defaultView結束。這個階段也稱為“起泡階段”(bubbling phase)。註冊到這個階段的事件監聽器應當在事件物件離開其目標之後處理該事件。

在某個階段的部分傳播路徑中還有未及(pending)事件目標,而且沒有通過呼叫Event.stopPropagation()停止事件物件傳播的情況下,實現應當按照下列步驟讓事件物件完成整個事件階段。

首先,實現應當確定當前目標。當前目標就是從開頭算起的下一個未及事件目標。從事件監聽器的角度看,當前目標應當是註冊了該監聽器的事件目標。

其次,實現應當確定當前目標的候選事件監聽器。這應當是一個註冊到當前目標的事件監聽器的列表,以它們註冊的先後順序為序。HTML5規範通過事件處理程式屬性來定義註冊的監聽器的順序。確定之後,候選事件監聽器不可改變;再新增或刪除監聽器都不會影響當前目標的候選事件監聽器。

最後,實現應當按順序處理所有候選事件監聽器,並在滿足下列條件的情況下觸發每個監聽器:

  • 事件物件的即時傳播未被停止
  • 監聽器註冊到了這個事件階段
  • 監聽器註冊到了這個事件型別

實現應當通過呼叫EventListener.handleEvent()方法,或者針對繫結的等價機制觸發監聽器。

在分派事件的最後一步,考慮到向後相容,實現應當重置事件物件的內部傳播(internal-propagation)與預設動作阻止(default-action-prevention)狀態。這樣就保證了同一個事件物件可以多次分派,同時也確保在分派事件之前阻止事件物件的傳播或預設動作。

在產生傳播路徑的過程中,如果defaultView實現了EventTarget介面,事件在捕獲階段defaultView傳播到document物件,在冒泡階段從document物件傳播到defaultView

無論與事件目標相關的是什麼事件流,上面定義的模型都應當適用。每種事件流應當定義確定傳播路徑的必要方式以及支援哪些事件階段。DOM事件流就是這個模型的一個應用:Node物件的傳播路徑應當由其Node.parentNode鏈,以及必要時文件的包含defaultView決定;所有事件都經過捕獲和目標階段;事件型別應當定義事件是否經過冒泡階段。DOM3 Load and Sava 規範定義了另一種事件模型。

DOM事件模型的實現應當可重入。事件監聽器可以執行額外分派事件的操作。這些事件以同步方式處理,導致觸發事件監聽器的事件傳播必須在新事件分派完成後恢復。

3.2 預設動作與可取消的事件

事件物件可以有一或多個與它們關聯的預設動作。這些動作是實現在分派事件物件的同時必須執行的。HTML5的表單元素就是一個例子。當使用者提交表單時(比如按下提交按鈕),HTML事件submit將被分派到表單元素,而這種事件型別的預設動作通常就是向Web伺服器傳送一次請求,請求的引數來自該表單。

預設動作應該在事件分派完畢之後再執行,但在某些例外情況下,也可以在分派事件之前執行。

某些事件物件是可取消的,這就意味著可以阻止預設動作發生,或者,如果預設動作在事件分派前執行,其影響可能會反轉。事件物件是否可取消,應當通過Event.cancelable屬性表示。事件監聽器可以呼叫Event.preventDefault()取消可取消事件物件的預設動作,可以在物件被分派後通過Event.defaultPrevented屬性,或者通過DOM應用本身所分派事件物件的EventTarget.dispatchEvent()方法的返回值,來確定事件是否已被取消。

本規範不會規定以程式設計方式查詢某事件物件是否具有相關預設動作的功能,或者為事件物件關聯新預設動作的功能。其他規範可能會為某些事件物件定義相關的預設動作。此外,實現可以根據自身的需要將某些預設動作關聯到某些事件。例如,有的實現可能會將滾動一定距離的文件檢視作為滑鼠滾輪事件的預設動作,而另一些實現則可能將縮放文件作為滑鼠滾輪事件的預設動作

3.3 同步和非同步事件

事件可以是同步的,也可以是非同步的。

同步事件(sync events)應該被當作一個“先進先出”的虛擬佇列,按即時發生的順序排列,這些事件與其他事件、與DOM的變化以及使用者互動有關。這個虛擬佇列中的每一個事件都應當等待前一個事件傳播完成或者被取消後再分派。某些同步事件由特定裝置或程式驅動,例如滑鼠事件;這些事件由專門為它們定義的事件順序演算法控制,使用者代理應當按照定義的順序分派這些事件。

例子:使用者雙擊文字選擇一個詞,然後按刪除鍵刪掉這個詞,會觸發以下同步事件序列:mousedown、mouseup、click、mousedown、mouseup、click、dblclick、select、keydown、DOMCharacterDataModified。這些事件按照使用者操作發生的順序依次觸發。

非同步事件(async events)可以作為操作完成的結果來分派,與其他事件、其他DOM變化,以及其他使用者操作無關。

例子:在載入文件期間,解析並執行了一行嵌入的指令碼。script元素的load事件是以非同步方式排隊觸發的。但是,因為它是非同步事件,它的順序涉及載入期間觸發的其他同步事件(比如HTML5定義的DOMContentLoaded事件)。

3.3.1 事件順序和事件迴圈

大多數事件是前後相繼發生的。HTML5採用了一種事件迴圈機制來定義其事件操作。所謂事件迴圈,就是把各種型別的事件裝入不同的任務佇列。本規範不採用這種事件迴圈機制來定義事件,但與該機制相容。本規範規定了一些與操作相關的事件順序

用HTML5的術語來講,滑鼠、鍵盤等這些外設都是獨立的任務源,分別按照與該裝置關聯的事件順序(演算法)定義的順序,為相應的任務佇列提供事件。每一個操作,例如焦點切換或組合輸入,也都如同相應任務佇列任務源。解析事件迴圈的方式遵循宿主語言(如HTML)的慣例。

警告!某些事件,比如以一定的順序按下“熱鍵”或“控制鍵”,可能會被作業系統或應用“吞掉”,從而打斷預期的事件順序。內容作者應該加強審校,以避免此類問題的影響。


相關連結:

相關文章