1. 程式人生 > >深入理解FLEX事件機制

深入理解FLEX事件機制

1.  AS3中的事件為什麼要分三個階段,即捕獲、目標、冒泡。

這要從FLEX使用的指令碼語言ActionScript3的老大哥Javascript說起,熟悉Javascript的朋友都知道在DOM中完整的事件生命週期包括捕獲、目標與冒泡三個階段。
事件的處理過程將從事件目標所在DOM層次的根節點開始,而不是從派發事件的目標開始,並且最終會回到根節點。

捕獲階段:事件從目標所在DOM樹的根節點依次往下層傳遞,一直到目標節點之前的過程。在這個過程中,事件將會被從根節點到事件目標之前的所有父節點物件所捕獲。如果事件在被註冊時設定了useCapture屬性為true,那麼它會被這期間的所有註冊了該事件的父元素物件依次派發。

目標階段:事件到達目標元素。目標元素如果註冊了該事件並且設定了useCapture屬性為false,則目標會派發該事件。

冒泡階段:事件到達目標元素後會接著通過DOM節點向上層父節點冒泡,並最終回到目標所在DOM樹的根節點的過程。在這個過程中如果事件是可冒泡事件(最典型的例子就是click事件)並且在被註冊時設定了useCapture屬性為false,那麼它會被這期間的所有註冊了該事件的父元素物件派發。

AS3也採用這套機制。上圖就是Adobe官方文件中的插圖。真個AS3事件的生命週期與DOM物件上JS事件的生命週期完全相同。
我認為這套機制這樣最大的好處就是可以減少了同一個UI樹上物件監聽器數量,從而帶來效能優化。

舉個簡單而常見的例子:
canvas上有n個button,如果給每個button註冊一個click事件,那麼記憶體中就需要維護n個listener。由於click事件是可冒泡事件,那麼更好的解決辦法就是給canvas註冊一個click事件,即

?
canvas.addEventListener(MouseEvent.CLICK, onMouseClickHandler);

調通過事件的冒泡階段由n個button共同的父元素canvas來派發click事件,並且可以通過事件引數的target屬性來決定呼叫哪個button對應的處理函式。這樣做的的好處就可以只註冊一個事件監聽從而代替為n個button註冊n個事件。

2.target和currentTarget究竟誰是事件的派發者?

target:事件指向的目標,但並非事件的派發者。 在一個事件整個生命週期中所有被派發的事件處理函式內部的target物件是一樣的,每個被監聽者派發的事件處理函式中的target一定都是同一個物件。
currentTarget:事件的派發者。呼叫註冊在事件上的回撥函式的那個物件,簡單的說就是派發事件的物件。

3.useCapture到底有何用?

首先看看AS3中為IEventDispatcher型別物件註冊事件監聽的介面(完全符合W3C標準)

?
addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void

最顯而易見的用處,如果沒有這個引數,那麼走完事件的生命週期,所有目標父節點上註冊的事件都會被執行兩次(捕獲階段一次+冒泡階段一次)!也正因為有useCapture這個引數,整個事件機制的運作才有了分支,或者更說是被分成兩個獨立部分。

拿上面“n個button的例子”來說,如果使用者點選某一個button將會觸發click事件,首先我們來看下整個click事件的生命週期中將會經歷的UI物件:

stage → canvas → button →  canvas → stage

然後我們再看分析一下事件流的三個階段。
首先是捕獲階段:
事件從該button所處顯示物件樹的根出發,也就是stage,stage上未註冊click事件,不會做出任何響應。
然後來到stage的子節點即canvas,注意我們在canvas上註冊了click事件,而此時click事件不會被派發,因為例子中我們註冊的click事件使用的是預設的useCapture=false引數,即click事件必須在目標和冒泡階段才能被派發。
然後進入目標階段:
儘管目標是button,但由於其並未註冊任何click事件監聽所以也不作出任何響應。
由於click事件是可冒泡事件,所以最後會進入冒泡階段:
從button到canvas,canvas上註冊了click事件監聽,所以canvas派發該事件,進入事件處理函式,此時target是button,currentTarget是canvas。

如果useCapture=true呢?
還是“n個button的例子”,我們給canvas註冊一個useCapture=true的click事件會怎麼樣呢即

?
canvas.addEventListener(MouseEvent.CLICK, onMouseClickHandler, true);

這樣有三點好處:

1. 事件不會經歷目標和捕獲階段,這樣只有當且僅當事件的target為監聽物件的子物件時,事件才會被派發。“n個button的例子”當監聽器的引數useCapture=true,使用者點選目標如果是canvas自己,則事件不會被派發。換言之只有點選canvas上的子物件,比如某個button或是canvas上的其他UIcomponent,click事件才會觸發。再解釋一遍為什麼吧。點選canvas本身,事件流首先進入捕獲階段從目標(canvas)所在顯示物件列表的根stage開始向下,來到canvas上,這時因為event.target==canvas,進入目標階段,儘管canvas註冊了click事件,但useCapture引數是true,那就是說事件只能在捕獲階段被派發,所以這個事件並不會被派發出來,也就是點選canvas沒有任何響應。
想象一下應用場景,比如那種點選小球會得分的遊戲,我們可以給小球的容器物件加上監聽,但又不想在事件處理函式中接判斷使用者是否點到了容器本身上,這時我們大可放心的給容器物件加上useCapture=true的點選事件監聽,讓事件在捕獲階段就派發出來,這樣就再也不用在函式中check來check去了,管它event.target是誰呢。
2. 不用考慮事件是否支援冒泡。
3. 由於少了兩個階段,必然少折騰一些傳遞。