js中事件(自定義事件)


  今天閑的蛋疼,我們來聊一聊web前端中的事件機制和自定義事件。靈感來自jQuery,在此感謝jQuery作者。

  首先,最開始。

<button id="button" type="button" onclick="alert('hello')">你好</button>

  這是我們在使用html寫頁面的時候最原生的事件觸發方式。上面那行代碼會生成一個按鈕,當我們點擊這個按鈕的時候就會彈出一個原生的彈窗,內容是hello。

  隨着技術的發展,我們認為事件要和html結構分開,於是就演化出了這么一種寫法。

<button id="button" type="button">你好</button>
<script>
var button = document.getElementById("button");
button.onclick = function(){
  alert("hello"); } </script>

  以上代碼的效果和第一個一樣,但是實現了事件與html的分離。

  和上面的代碼采用一樣的原理,可以為各種各樣元素添加各種各樣事件。比如說keyup、mouseover等。

  那我們對js最原生的事件有了一定的了解后,我們就會想,我們能不能自定義事件呢?比如說,我們希望在按一個按鈕的時候觸發一個save事件。我們發現原生的js中沒有save事件,怎么辦,難道就這么放棄嗎?

  於是我們就考慮了,事件的本質在於消息的傳遞。那我們把save寫成一個函數,當我們點擊按鈕的時候執行該函數,不就變相的實現了這個自定義事件嗎?

<button id="button" type="button">你好</button>
<script>
var button = document.getElementById("button");
button.onclick = function(){
     save();
}
var save = function(){
     alert("save");
}
</script>

  是啊,實現是實現了,但我們就覺得這個方法好挫啊,而且我們如果想要為save定義多個事件,就會發現,后一個事件會覆蓋前一個事件這就相當的蛋疼了。

  那我們可不可以這樣,將save事件弄成一個函數數組,在觸發的時候順序觸發這個數組中的每一個函數,這樣我們不就可以觸發多個方法了?然后我們如果需要為該事件添加新的方法,只要在這個數組中添加新的項就可以啦。

<button id="button" type="button">你好</button>
<script>
var button = document.getElementById("button");
button.onclick = function(){
    trigger(save);
}
var trigger = function(){
    for(var i in save){
        save[i]();
    }
}
var save1 = function(){
    alert("save1");
}
var save2 = function(){
    alert("save2");
}
var save = [save1,save2];
</script>

  以上的代碼會順序彈出save1和save2。使用同樣的方法我們可以為原生的事件添加多個函數方法。(是不是有點類似於addEventListener和attachEvent?)

  看着上面的代碼還是有點不爽,為什么呢?因為沒有上面提到的那兩個方法帥呀。哈哈。

  我們的方法優勢在於可以添加自定義事件,而原生的方法不但執行效率比我們高,使用也比咱們便利,感覺好不爽。

  我們重新設計一下,剛才說原生的方法比咱們便利,那我們就進行統一化盡力提高便利性。我們剛才的分析中提到了,原生的事件和自定義的事件都可以通過以上的方法來玩。那我們就不要管是原生的還是自定義的了。

  事件可能有哪幾種操作,我這里只想到了,添加、移除、觸發以及掛靠到原生事件上。那我們可以定義addEvent()添加事件、removeEvent()移除事件、trigger()觸發事件、dispatchEvent()掛靠事件。

  這里就不提供源代碼了,如果有興趣可以去查看jQuery源碼,推薦看低版本,比如說1.0.4那里的事件機制是最原始的,也是最易懂的。

  addEvent()

    1、需要檢測事件數組是否存在,如果不存在定義一個數組,這個數組用於存放事件的所有方法,執行2。否則執行3

    2、將掛載在元素上的事件方法添加到該數組中。執行3

    3、將函數參數中傳入的事件方法添加到事件數組中。執行4

    4、將函數數組掛載帶元素上。

  removeEvent()

    1、通過傳入參數找到事件數組中想要移除的事件方法。執行2

    2、移除對應事件方法。

  trigger()

    1、依次執行事件數組中的每一個函數方法即可。

  dispatchEvent()

    1、將事件數組的觸發函數掛載到元素的執行函數上。只要完成下面代碼的效果即可。

<button id="button" type="button">你好</button>
<script>
var button = document.getElementById("button");
var handler = function(type){
    //這是事件數組觸發函數
}
button.onclick = handler(click);
</script>

  寫了這么多,還是感覺好不爽怎么辦,為什么呢?我要是一次性觸發了好多好多事件,那我們就不好理解這些事件的執行順序了。

  於是,我們可以設置一個全局的事件隊列,觸發函數觸發事件的時候,不直接執行函數方法,而是在事件隊列中添加一個信號。而全局的事件隊列定時的檢測是否有新的事件產生(比如100毫秒檢測一次,實際上不會帶來多大的系統開銷。)如果有新的事件產生,就執行對應的函數方法。這樣的好處在於有利於用戶控制每個事件的執行順序(只要調整事件隊列中的順序即可),從而達到很多意想不到的效果。

--------------------------------------------------分割線------------------------------------------

后面會陸陸續續將jquery1.0.0版本中的event源碼分析發上來。

--------------------------------------------------分割線-------------------------------------------

/*add函數用於添加事件,和上文中的addEvent用處相同。*/
        add: function(element, type, handler) {
            if ( jQuery.browser.msie && element.setInterval != undefined )
                element = window;
            /*為每一個函數(handler)分配一個不重復的id作為訪問句柄。
             如果之前已經添加過該函數了就不在進行分配*/
            if ( !handler.guid )
                handler.guid = this.guid++;
            /*每一個元素第一次進來的時候都會訪問它,之后就再也不會訪問。
              用於初始化一個位於元素下的事件對象。*/
            if (!element.events)
                element.events = {};
            /*type是事件類型,這里的目的是將事件對象中屬於本次添加事件
              類型的事件對象緩存下來。如果第一次添加該類型的事件則
              handlers未定義。*/
            var handlers = element.events[type];
            /*當第一次添加該類型事件時,初始化該類型事件對象。注意,
              如果元素原生事件上如果有對應類型的事件,記得把它存下來。*/
            if (!handlers) {
                handlers = element.events[type] = {};
                if (element["on" + type])
                    handlers[0] = element["on" + type];
            }
            /*將本次添加的函數的句柄保存到對應的函數對象中*/
            handlers[handler.guid] = handler;
            console.log(handlers[handler.guid])
            /*將事件分發函數掛載到元素的原生事件上*/
            element["on" + type] = this.handle;
            /*初始化一個全局的事件隊列,將元素壓到隊列中,表示該元
              素可以觸發對應類型的事件。這里是為了觸發的方便考慮。*/
            if (!this.global[type])
                this.global[type] = [];
            this.global[type].push( element );
        },
        /*add函數執行完畢以后會生成以下內容
        *1、函數的句柄中會生成一個guid
        *例如 var fn1 = function(){}
        *     $.event.add( window, "click", fn1 );
        *執行以后console.log(fn1);//這里會有一個數字,不一定是
        *多少,但不重復,按事件的添加順序,從1開始。
        *2、元素下會生成一個events
        *例如
        $("div").bind("mouseover",function(){
            console.log(1);
        }).bind("mouseover",function(){
            console.log(2);
        });
        $("div").bind("click",function(){
            console.log(1);
        }).bind("click",function(){
            console.log(2);
        });
        *會生成events形如
        *{
        *    "click":{4:function(){console.log(1);},5:function(){console.log(2);}},
        *    "mouseover":{2:function(){console.log(1);},3:function(){console.log(2);}
        *}
        *這里之所以沒有1,是因為通常情況下會在window下有一個load事
        * 件,那個是最先加載的。
        *3、console.log(handlers[handler.guid])//function()就
        *是本次添加進來的事件函數的句柄
        *4、div.onclick指向事件分發函數
        *5、global中形如{"click":[div,div],"mouseover":[div,div]}
        */

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM