JavaScript事件在WebKit中的處理流程研究
本文主要探討了JavaScript事件在WebKit中的註冊和觸發機制。
JS事件有兩種註冊方式: 通過DOM節點的屬性添加或者通過node.addEventListener()函數註冊;
通過DOM節點的屬性添加如下所示,節點的屬性採用on後面緊接event name的形式,比如onclick, onload;
<script type=text/javascript> function listener(e){ alert(hello world!); }</script>click
通過addEventListener()函數註冊的形式如下, 其完整的形式是:target.addEventListener(type, listener[, useCapture]);其中type為事件類型,listener為響應函數, useCapture表示是否在capture階段觸發,如果不指定,則為false;
button<script type=text/javascript> document.getElementById('button').addEventListener(click, listener);</script>
WebKit中事件相關的類別關係如所示:
1. EventTargetDatatMap: 全域對應表,建立了Node與EventTargetData之間的映射關係 ;
2. EventTargetData: 成員變數firingEventIterators是Vector, 用於記錄正在觸發的事件類型,當該Vector非空時,也表示當前正處於firing階段; 成員變數eventListenerMap是EventlListenerMap類型;
3. EventlListenerMap:按事件類型分類儲存了EventListeners; 成員變數m_entires是Vector,其中每一項可以簡化為std::pair類型;
4. JSLazyEventListener: 最終響應事件觸發的對象; 儲存了JS執行的基本資料(源碼或者JSObject類型的函數對象);
第一種情況下,開始事件註冊的時機是發生在頁面解析階段,當建立對了button元素以後,解析到onclick屬性,會根據屬性值建立對應的EventListener; 這種情況下的EventListener僅儲存了JS源碼(還沒有轉換成JSC虛擬機器內部的函數對象), 並將EventListener添加到全域Hash表中;
第二種情況下,JS在虛擬機器中執行到”addEventListener()時,會根據JSBindings建立的映射關係,最終調用到WebCore中的native實現Node::addEventListener(), 該函數會根據虛擬機器中傳遞過來的函數對象建立EventListener,並在全域Hash表中建立起target node與EventListener(即這裡的button)的映射關係;
是兩種情況下,事件註冊的流程對比:
事件觸發流程有以下幾個步驟:
1. 找到響應事件的target node: 如果是使用者互動事件,通過Hit Test演算法確定; 如果是瀏覽器內部產生的事件,一般有固定的響應節點,比如load事件的target node是body節點;
2. 事件分發:事件在document與target之間按照(capture, at_target, bubble)的順序進行分發,capture按照從根節點document到子節點target的路徑,而bubble則相反;
3. 事件響應:分發流程中,如果事件分發到的當前節點註冊了該類型的事件,並且useCapure與事件的分發的順序一致(即capture階段時,當前節點註冊了useCapture == true的事件), 則進行事件響應; 事件響應分成兩步: (1) 從全域對應表中找到當前node對應的EventListeners;(2)將EventListeners封裝的JS(源碼或者JSC的函數對象)拋到JS虛擬機器中執行(是mouseup事件的觸發時序):
如前所述,屬性中註冊的事件在EventListener中僅儲存了源碼,所以開始執行之前會對源碼進行必要的轉換,格式化成如下形式:
(function(event) {listener(event)})
簡單來講,事件註冊是建立node與響應函數的映射關係的過程 ,這種映射關係基於事件類型進行分類; 而事件觸發則是基於這種映射關係,在不同階段(capture, bubble)響應註冊函數的過程;