解密jQuery事件核心 - 自定義設計(三)

接上文http://www.cnblogs.com/aaronjs/p/3447483.htmlcss

 本文重點:自定義事件html

「經過事件機制,能夠將類設計爲獨立的模塊,經過事件對外通訊,提升了程序的開發效率。」node

對象之間經過直接方法調用來交互

1)對象A直接調用對象B的某個方法,實現交互;直接方法調用本質上也是屬於一種特殊的發送與接受消息,它把發送消息和接收消息合併爲一個動做完成;jquery

方法調用方和被調用方被緊密耦合在一塊兒;由於發送消息和接收消息是在一個動做內完成,因此沒法作到消息的異步發送和接收;ajax

2)對象A生成消息->將消息通知給一個事件消息處理器(Observable)->消息處理器經過同步或異步的方式將消息傳遞給接收者;api

這種方式是經過將消息發送和消息接收拆分爲兩個過程,經過一箇中間者來控制消息是同步仍是異步發送;數組

在消息通訊的靈活性方面比較有優點,可是也帶來了必定的複雜度。可是複雜度通常能夠由框架封裝,消息的發送方和接收方仍然能夠作到比較簡單;瀏覽器

總的來講就是一種鬆耦合的處理,2個對象之間有太多緊密的直接關聯,應該要考慮經過消息通訊解耦,從而提升應用程序的可維護性和重用性緩存

 


在JS中,消息的通知是經過事件表達的,當代碼庫增加到必定的規模,就須要考慮將行爲和自定義事件進行解耦。app

瞭解自定義事件的概念

  • 相似DOM的行爲:你在DOM節點(包括document對象)監聽並觸發自定義事件。這些事件既能夠冒泡,也能夠被攔截。這正是Prototype、jQuery和MooTools所作的。若是事件不能擴散,就必須在觸發事件的對象上進行監聽。
  • 命名空間:一些框架須要你爲你的事件指定命名空間,一般使用一個點號前綴來把你的事件和原生事件區分開。
  • 自定義額外數據:JavaScript框架容許你在觸發自定義事件時,向事件處理器傳送額外的數據。jQuery能夠向事件處理器傳遞任意數量的額外參數。
  • 通用事件API:只用Dojo保留了操做原生DOM事件的正常API。而操做自定義事件須要特殊的發佈/訂閱API。這也意味着Dojo中的自定義事件不具備DOM事件的一些行爲(好比冒泡)。 
  • 聲明:咱們每每須要在預約義的事件中加入一些特殊的變化(例如,須要Alt鍵按下才能觸發的單擊事件),MooTools運行你定義此類自定義事件。此類事件須要預先聲明,即使你只是聲明他們的名字。任何未聲明的自定義事件不會被觸發。

理論太抽象,看看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.trigger 與 document.dispatchEvent 區分

瀏覽器提供自定義事件接口,那麼就jQuery是否是利用這個原理呢?


 

第一種狀況:DOM-events使用jQuery觸發。 觸發不會處理經過addEventListener綁定的事件
另外一種:DOM-events觸發使用本機createEvent / dispatchEvent方法與用jQuery.bind註冊事件偵聽器

這是一個問題,若是你與非jQuery代碼混合jQuery代碼。例如,jQuery移動模擬orientationchange事件基於窗口尺寸和大小事件但它使用jQuery觸發orientationchange事件。 觸發,所以不調用本機事件偵聽器。

 


trigger的幾種常見用法

1.經常使用模擬

在jQuery中,可使用trigger()方法完成模擬操做。例如可使用下面的代碼來觸發id爲btn按鈕的click事件。

$("#btn").trigger("click");

這樣,當頁面加載完畢後,就會馬上輸出想要的效果。

也能夠直接用簡化寫法click(),來達到一樣的效果:

$("#btn").click();
 

2.觸發自定義事件

trigger()方法不只能觸發瀏覽器支持的具備相同名稱的事件,也能夠觸發自定義名稱的事件。

例如爲元素綁定一個「myClick」的事件,jQuery代碼以下:

$("#btn").bind("myClick", function () {
    $("#test").append("<p>個人自定義事件。</p>");
});

想要觸發這個事件,可使用下面的代碼來實現:

$("btn").trigger("myClick");
 

3.傳遞數據

trigger(tpye[,datea])方法有兩個參數,第一個參數是要觸發的事件類型,第二個單數是要傳遞給事件處理函數的附加數據,以數組形式傳遞。一般能夠經過傳遞一個參數給回調函數來區別此次事件是代碼觸發的仍是用戶觸發的。

下面的是一個傳遞數據的例子:

$("#btn").bind("myClick", function (event, message1, message2) { //獲取數據
    $("#test").append("p" + message1 + message2 + "</p>");
});
$("#btn").trigger("myClick",["個人自定義","事件"]); //傳遞兩個數據
$(「#btn」).trigger(「myClick」,["個人自定義","事件"]); //傳遞兩個數據

 

4.執行默認操做

triger()方法觸發事件後,會執行瀏覽器默認操做。例如:

$("input").trigger("focus");

以上代碼不只會觸發爲input元素綁定的focus事件,也會使input元素自己獲得焦點(瀏覽器默認操做)。

若是隻想觸發綁定的focus事件,而不想執行瀏覽器默認操做,可使用jQuery中另外一個相似的方法-triggerHandler()方法。

$("input").triggerHandler("focus");

該方法會觸發input元素上綁定的特定事件,同時取消瀏覽器對此事件的默認操做,即文本框指觸發綁定的focus事件,不會獲得焦點。

 


jQuery自定義事件原理

根據API,trigger支持 .trigger()事件會在DOM樹上冒泡,在事件處理程序中返回false或調用事件對象中的.stopPropagation() 方法可使事件中止冒泡

看看demo


 

 

按照tigger綁定的方式

$('ele').on('aaa',function(){})
$('ele').on('click',function(){})

第一種是自定義的事件名aaa,第二種是瀏覽器事件click

根據trigger的API,會處理冒泡這個關鍵點,

 


trigger須要處理的問題

1.模擬事件對象,用戶模擬處理中止事件冒泡

這個很明瞭,由於不是經過瀏覽器系統觸發的,而是自動觸發的,因此這個事件對象要如何處理?

2.區分事件類型,觸發標準的瀏覽器事件 和 自定義事件名綁定的處理程序。

例如:事件名稱+命名空間

p4.on('click.aaa.ccc',function(e,vv,c){
       console.log('p4')
   })

    p4.trigger('click.aaa')

因此trigger觸發的時

3.模擬冒泡機制

那麼瀏覽器click類型,天然是自己支持冒泡這樣的行爲,經過stopPropagation阻止便可

固然一些事件,如focusin和 blur自己不冒泡,但 jQuery 爲了跨瀏覽器一致性, jQuery 須要在這些事件上模擬了冒泡行爲,jQuery要如何處理?

那麼若是是自定義的aaa的事件名,又如何處理冒泡?


源碼解讀

附上源碼

trigger源碼

 

初看trigger源碼部分,真有點暈,處理的hack太多了,可是仔細規劃下,無非就是解決上面提到的幾點問題

 

1 命名空間的過濾

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判斷有.是索引,即存在命名空間,而後踢掉第一個事件名

 

2 模擬事件對象

event = event[ jQuery.expando ] ?
            event :
            new jQuery.Event( type, typeof event === "object" && event );

在on機制裏面就分析了,其實就是jQuery.Event類了

 

4 返回的事件數據合集

data = data == null ?
            [ event ] :
            jQuery.makeArray( data, [ event ] );

因此data就是事件回調返回的[event,data],若是傳遞了數據就合併到data中

 

5  jQuery.event.special

這個在不少地方用到,這個是用來作模擬事件的,好比提到的模擬聚焦冒泡之類的,下章再講

 

6 模擬事件冒泡

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 );
            }
        }

其實大體的手法都差很少了,無非就是遍歷全部的元素節點了,排個隊列出來

image

若是循環中最後一個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很好的模擬了瀏覽器事件流程,可是美中不足的是對象的事件混淆其中 這就形成了 觸發對象事件的時候 最後會調用對象的相應方法

相關文章
相關標籤/搜索