今天閑的蛋疼,我們來聊一聊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]}
*/
