標籤:
本課還是來講解一下jQuery是如何?它的事件系統的。上一課主要講了jQuery.event.add的源碼解析,這幾課主要針對jQuery源碼進行分析,如果不需要理解jQuery是如何?事件系統機制的,可以忽略。這一課我們先來講一下jQuery.event.remove的源碼解讀。
remove方法的目的是,根據使用者傳參,找到事件隊列,從裡面把匹配的handleObj對象移除,在參數不足的情況下,可能移除多個或所有的handleObj。當隊列的長度為0(當某事件的事件處理函數數組為空白時,就代表此類型事件沒有事件處理函數了,因此移除此事件)時,就移除相應的事件,當events為空白對象(events是元素繫結的所有事件類型的集合,當它為空白時,就代表元素沒有綁定任何事件,那麼就沒有必要再儲存元素在緩衝系統的唯一標識了)時,就清除掉UUID(也就是元素在緩衝系統中唯一的標識)。
接下來,我們來看它的源碼:
remove = function(elem,types,handler,selector){
var t,tns,type,origType,namespaces,origCount,j,events,special,eventType,handleObj,
elemData = jQuery.hasData(elem) && jQuery._data(elem);
if(!elemData || !(events = elemData.events)){ //如果緩衝系統中沒有有關這個元素的資料,或者不支援添加自訂屬性(jQuery._data(elem))就直接返回。如果支援添加自訂屬性,並且緩衝系統中有這個元素的資料,但如果有關這個元素的事件處理函數為空白,也直接返回,因為沒有什麼可以移除了,此元素本身就沒有綁定任何事件處理函數。
return;
}
types = jQuery.trim(hoverHack(types||"")).split(" "); //處理hover事件的,它需要轉換成mouseenter和mouseleave兩個事件
for(t=0;t<types.length;t++){
....
type = origType = types[t];
....
if(!type){ //如果沒有指定需要移除的事件類型,那麼就移除所有事件類型或所有與此命名空間有關的事件類型
for( type in events){
jQuery.event.remove(elem,type,handler,selector,true);
}
continue;
}
special = jQuery.event.special[type] || {}; //處理特殊的事件類型,並不是所有的事件都能直接使用,比如Firefox下沒有mousewheel,需要用DOMMouseScroll冒充
type = (selector ? special.delegateType: special.bindType) || type; //對於事件代理的綁定,需要針對不能冒泡的事件進行處理,取得真正用於綁定的事件類型
eventType = events[type] || []; //取得此事件類型的裝載事件描述對象的數組
origCount = eventType.length;
for(j=0;j<eventType.length;j++){ //移除數組中合格事件描述對象
handleObj = eventType[j];
if(origType===handleObj.origType&& //比較事件類型是否一致
!handler || handler.guid === handleObj.guid&& //如果沒有傳入事件處理函數,或者傳入了,並且guid等於事件描述對象的guid,就繼續判斷
!namespace || namespace.test(handleObj.namespace) && //如果types有命名空間,就判斷他們的命名空間是否一樣
!selector || selector === handleObj.selector //如果是事件代理,就判斷事件代理是否一致,
){
eventType.splice(j--,1); //移除這個事件描述對象
if(handleObj.selector){ //如果是事件代理的事件描述對象被移除,就把delegateCount減一
eventType.delegateCount--;
}
.....
}
}
if(eventType.length === 0 && origCount ! ==eventType.length){//如果事件描述對象數組為空白,並且是因為上面for迴圈移除之後才為空白的,就進入if語句。之所以加上後面的那個判斷,是因為eventType本身如果是空的,並不是通過for迴圈移除的,那進入到if語句,會出現死迴圈
.....
delete events[type]; //刪除此類型事件的屬性。因此此類型事件沒有事件處理函數了,被全部刪除了
}
}
if(jQuery.isEmptyObject(events)){//如果此元素的所以事件類型的屬性都被刪除了,那麼events將是一個Null 物件
delete elemData.handle; //既然此元素的所有事件類型都沒有了,就證明此元素沒有綁定任何事件,那麼也要刪除通過add添加給它的回調方法(此回調方法會調用此元素的所有回調方法)。
jQuery.removeData(elem,"events",true); //移除這個元素在緩衝系統中的events空間(清楚這個元素在緩衝空間的UUID)。
}
}
接下來,我們來講一下jQuery.event.dispatch的源碼:
jQuery就是利用這個dispatch方法,從緩衝體中的events對象取得對應隊列,然後修複事件對象event,傳入使用者的回調中執行,根據回調的傳回值決定是否停止迴圈,是否阻止預設行為和事件冒泡。
dispatch = function(event){
event = jQuery.event.fix(event || window.event); //解決W3C和IE事件對象的差異,比如:W3C,event.target等於IE的event.srcElement
var i,j,cur,ret,selMatch,matched,matched,handleObj,sel,related,
handlers = ((jQuery._data(this,"events") || {}) [event.type] || [], //得到緩衝系統中this(操作的元素)的events對象,然後取它的事件類型(你觸發的event的類型)屬性對象,這個屬性對象就是裝有事件描述對象的數組。
delegateCount = handlers.delegateCount, //得到這個事件描述對象數組有多少個使用了事件代理
args = core_slice.call(arguments), //[].slice.call(arguments),轉換成數組
...
special = jQuery.event.special[event.type] || {}, //解決特殊的事件類型
handlerQueue = [];
....
if(delegateCount && !(event.button && event.type === "click")){ //這裡先講一下event.button這個屬性,這個屬性,代表使用者點擊了滑鼠的左鍵還是右鍵,還是捲軸,等等。IE與標準瀏覽器的值不一樣,比如,左鍵IE下是1,而其他瀏覽器是0等。但是在上面的代碼中已經修複了event對象,因此IE下,點擊左鍵也就是0.因此上面的if語句的意思是:如果有事件代理,並且是左鍵點擊(event.button=0),就進入if語句。
for(cur = event.target ; cur != this;cur = cur.parentNode || this){ //從事件來源開始,遍曆其所有的祖先,一直到綁定事件的元素
if(cur.disabled !==true || event.type !== click){//如果元素是被禁止的,並且事件類型是click,就不進入if語句。意思就是不能在被禁止的元素上觸發click事件
....
matches = []; //用於收集合格事件描述對象
for(i=0;i<delegateCount;i++){ //使用事件代理的事件描述對象總是排在數組前面
handleObj = handlers[i];
sel = handleObj.selector; //擷取這個事件描述對象的執行內容
if(selMatch[sel] ===undefined) { //第一次時,selMatch[sel]是undefined
selMatch[sel] = jQuery.find(sel,this,null,[cur]).length; //sel,當前事件描述對象的執行內容,this事件觸發的元素,cur當前遍曆的元素。如果cur當前遍曆的元素匹配,就為真,不匹配就為假
}
if(selMatch[sel]){ //如果當前元素在事件描述對象中匹配
matches.push(handleObj); //就把這個事件描述對象收集起來。有多少元素在事件描述對象數組中匹配,就收集多少個事件描述對象。
}
}
if(matches.length){
handlerQueue.push({elem:cur,matches:matches});//如果當前遍曆的元素,匹配事件描述對象數組中的一個或以上。就把這些組裝成一個jsonObject Storage Service起來。
}
}
}
}
if(handlers.length > delegateCount){ //如果有不是事件代理的事件描述對象,將把這些事件描述對象與綁定事件的元素組裝成json,儲存起來
handlerQueue.push({elem:this,matches:handlers.slice(delegateCount)});
}
for(i=0;i<handlerQueue.length && !event.isPropagationStopped();i++){ //遍曆handlerQueue數組,條件是event沒有停止冒泡。
matched = handlerQueue[i];
event.currentTarget = matched.elem; //事件冒泡到的當前目標元素。
for(j=0;j<matched.matches.length && !event.isImmediatePropagationStopped();j++){//遍曆當前目標元素中所有與event.type同類型的事件描述對象數組。條件是使用者沒有執行停止冒泡的方法。(使用者調用stopImmediatePropagation方法,isImmediatePropagationStopped方法就會返回true)
handleObj = matched.matches[j] ; //取到這個事件描述對象
.....
event.data = handleObj.data;
event.handleObj = handleObj; //把事件描述對象賦給事件的handleObj屬性
ret =( (jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler ).apply(matched.elem,args) ;// 先看此事件描述對象中的事件類型是否是特殊事件,如果是,就調用它的handle方法。如果不是,就直接呼叫事件描述對象的handler方法。執行內容是在當前遍曆的目標元素中。
if(ret!==undefined){
event.result = ret; //如果事件處理函數執行後,傳回值不是undefined,就賦給event的result屬性
if(ret === false){ //如果事件處理函數返回false
event.preventDefault(); //阻止預設事件
event.stopPropagation(); //停止冒泡
}
}
}
}
return event.result; //返回事件處理函數執行後的傳回值。
}
dispatch的痛點在於,類比事件傳播的機制,其實就是事件冒泡的機制。
加油!
第二十四課:事件系統3