接上文http://www.cnblogs.com/aaronjs/p/3447483.htmlcss
本文重點:自定義事件html
「經過事件機制,能夠將類設計爲獨立的模塊,經過事件對外通訊,提升了程序的開發效率。」node
1)對象A直接調用對象B的某個方法,實現交互;直接方法調用本質上也是屬於一種特殊的發送與接受消息,它把發送消息和接收消息合併爲一個動做完成;jquery
方法調用方和被調用方被緊密耦合在一塊兒;由於發送消息和接收消息是在一個動做內完成,因此沒法作到消息的異步發送和接收;ajax
2)對象A生成消息->將消息通知給一個事件消息處理器(Observable)->消息處理器經過同步或異步的方式將消息傳遞給接收者;api
這種方式是經過將消息發送和消息接收拆分爲兩個過程,經過一箇中間者來控制消息是同步仍是異步發送;數組
在消息通訊的靈活性方面比較有優點,可是也帶來了必定的複雜度。可是複雜度通常能夠由框架封裝,消息的發送方和接收方仍然能夠作到比較簡單;瀏覽器
總的來講就是一種鬆耦合的處理,2個對象之間有太多緊密的直接關聯,應該要考慮經過消息通訊解耦,從而提升應用程序的可維護性和重用性緩存
在JS中,消息的通知是經過事件表達的,當代碼庫增加到必定的規模,就須要考慮將行爲和自定義事件進行解耦。app
理論太抽象,看看jQuery框架中如何使用事件
jQuery的事件自定義事件仍是經過on綁定的,而後再經過trigger來觸發這個事件
//給element綁定hello事件 element.bind("hello",function(){ alert("hello world!"); }); //觸發hello事件 element.trigger("hello");
這段代碼這樣寫彷佛感受不出它的好處,看了下面的例子也許你會明白使用自定義事件的好處了:
咱們已一個選項卡的插件爲例:
咱們讓ul列表來響應點擊事件,當用戶點擊一個列表項時,給這個列表項添加一個名爲active的類,同時將其餘列表項中的active類移除,
以此同時讓剛剛點擊的列表對應的內容區域也添加active類。
HTML:
<ul id="tabs"> <li data-tab="users">Users</li> <li data-tab="groups">Groups</li> </ul> <div id="tabsContent"> <div data-tab="users">part1</div> <div data-tab="groups">part2</div> </div>
jQuery
$.fn.tabs=function(control){ var element=$(this); control=$(control); element.delegate("li","click",function(){ var tabName=$(this).attr("data-tab"); //點擊li的時候觸發change.tabs自定義事件 element.trigger("change.tabs",tabName); }); //給element綁定一個change.tabs自定義事件 element.bind("change.tabs",function(e,tabName){ element.find("li").removeClass("active"); element.find(">[data-tab='"+ tabName +"']").addClass("active"); }); element.bind("change.tabs",function(e,tabName){ control.find(">[data-tab]").removeClass("active"); control.find(">[data-tab='"+ tabName +"']").addClass("active"); }); //激活第一個選項卡 var firstName=element.find("li:first").attr("data-tab"); element.trigger("change.tabs",firstName); return this; };
從上面的例子咱們能夠看到使用自定義事件回調使得選項卡狀態切換回調彼此分離,讓代碼變得整潔易讀。
$("ul#tabs").tabs("#tabsContent");
瀏覽器提供自定義事件接口,那麼就jQuery是否是利用這個原理呢?
第一種狀況:DOM-events使用jQuery觸發。 觸發不會處理經過addEventListener綁定的事件
另外一種:DOM-events觸發使用本機createEvent / dispatchEvent方法與用jQuery.bind註冊事件偵聽器
這是一個問題,若是你與非jQuery代碼混合jQuery代碼。例如,jQuery移動模擬orientationchange事件基於窗口尺寸和大小事件但它使用jQuery觸發orientationchange事件。 觸發,所以不調用本機事件偵聽器。
在jQuery中,可使用trigger()方法完成模擬操做。例如可使用下面的代碼來觸發id爲btn按鈕的click事件。
$("#btn").trigger("click");
這樣,當頁面加載完畢後,就會馬上輸出想要的效果。
也能夠直接用簡化寫法click(),來達到一樣的效果:
$("#btn").click();
trigger()方法不只能觸發瀏覽器支持的具備相同名稱的事件,也能夠觸發自定義名稱的事件。
例如爲元素綁定一個「myClick」的事件,jQuery代碼以下:
$("#btn").bind("myClick", function () { $("#test").append("<p>個人自定義事件。</p>"); });
想要觸發這個事件,可使用下面的代碼來實現:
$("btn").trigger("myClick");
trigger(tpye[,datea])方法有兩個參數,第一個參數是要觸發的事件類型,第二個單數是要傳遞給事件處理函數的附加數據,以數組形式傳遞。一般能夠經過傳遞一個參數給回調函數來區別此次事件是代碼觸發的仍是用戶觸發的。
下面的是一個傳遞數據的例子:
$("#btn").bind("myClick", function (event, message1, message2) { //獲取數據 $("#test").append("p" + message1 + message2 + "</p>"); }); $("#btn").trigger("myClick",["個人自定義","事件"]); //傳遞兩個數據 $(「#btn」).trigger(「myClick」,["個人自定義","事件"]); //傳遞兩個數據
triger()方法觸發事件後,會執行瀏覽器默認操做。例如:
$("input").trigger("focus");
以上代碼不只會觸發爲input元素綁定的focus事件,也會使input元素自己獲得焦點(瀏覽器默認操做)。
若是隻想觸發綁定的focus事件,而不想執行瀏覽器默認操做,可使用jQuery中另外一個相似的方法-triggerHandler()方法。
$("input").triggerHandler("focus");
該方法會觸發input元素上綁定的特定事件,同時取消瀏覽器對此事件的默認操做,即文本框指觸發綁定的focus事件,不會獲得焦點。
根據API,trigger支持 .trigger()
事件會在DOM樹上冒泡,在事件處理程序中返回false
或調用事件對象中的.stopPropagation()
方法可使事件中止冒泡
看看demo
按照tigger綁定的方式
$('ele').on('aaa',function(){}) $('ele').on('click',function(){})
第一種是自定義的事件名aaa,第二種是瀏覽器事件click
根據trigger的API,會處理冒泡這個關鍵點,
這個很明瞭,由於不是經過瀏覽器系統觸發的,而是自動觸發的,因此這個事件對象要如何處理?
例如:事件名稱+命名空間
p4.on('click.aaa.ccc',function(e,vv,c){ console.log('p4') }) p4.trigger('click.aaa')
因此trigger觸發的時
那麼瀏覽器click類型,天然是自己支持冒泡這樣的行爲,經過stopPropagation阻止便可
固然一些事件,如focusin和 blur自己不冒泡,但 jQuery 爲了跨瀏覽器一致性, jQuery 須要在這些事件上模擬了冒泡行爲,jQuery要如何處理?
那麼若是是自定義的aaa的事件名,又如何處理冒泡?
附上源碼
trigger源碼
初看trigger源碼部分,真有點暈,處理的hack太多了,可是仔細規劃下,無非就是解決上面提到的幾點問題
if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); }
按照規範p4.trigger('click.aaa.ccc'),'click.aaa.ccc' 就是事件+命名空間的組合
判斷也挺巧妙,indexOf判斷有.是索引,即存在命名空間,而後踢掉第一個事件名
event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event );
在on機制裏面就分析了,其實就是jQuery.Event類了
data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] );
因此data就是事件回調返回的[event,data],若是傳遞了數據就合併到data中
這個在不少地方用到,這個是用來作模擬事件的,好比提到的模擬聚焦冒泡之類的,下章再講
trigger與triggerHandler的本質區別實如今這裏了
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( tmp === (elem.ownerDocument || document) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } }
其實大體的手法都差很少了,無非就是遍歷全部的元素節點了,排個隊列出來
若是循環中最後一個cur是document,那麼事件是須要最後觸發到window對象上的,
將window對象推入元素隊列
爲何最後要加window?
7 處理事件
接下來的處理邏輯,無非就是遍歷每一個節點,取出對應節點上的事件句柄,並確保事件不須要阻止冒泡
i = 0; while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); } }
固然每一個元素上可能有多個事件,因此先肯定事件綁定類型是delegateType仍是bindType
檢測緩存中該元素對應事件中包含事件處理器,
有則取出主處理器(jQuery handle)來控制全部分事件處理器
因此最終代碼又走到了
handle.apply(cur, data);
其實就是交給了事件派發管理了
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
這時候事件就是按照dispatch的觸發規則,自行處理了,若是是瀏覽器事件就會按照dispatch處理冒泡了,自定義的就過濾了
因此jQuery的結構 是一層套一層,必需要從頭看起來知道流程
還有一部分代碼,須要在特定的環境下才會觸發的,遇到的時候在說
因此整個trigger的核心,仍是圍繞着數據緩存在處理的,經過on機制在jQuery.event.add的時候預處理好了
最終經過jQuery.event.dispatch派發
經過trigger很好的模擬了瀏覽器事件流程,可是美中不足的是對象的事件混淆其中 這就形成了 觸發對象事件的時候 最後會調用對象的相應方法