正文 本文所涉及到的jQuery版本是3.1.1,可以在壓縮包中找到event模塊。該篇算是閱讀筆記,jQuery代碼太長。。。。 Dean Edward的addEvent.js 相對於zepto的event模塊來說,jQuery的event那真是難讀了很多,先從大神Dean Edward的addE ...
正文
本文所涉及到的jQuery版本是3.1.1,可以在壓縮包中找到event模塊。該篇算是閱讀筆記,jQuery代碼太長。。。。
Dean Edward的addEvent.js
相對於zepto的event模塊來說,jQuery的event那真是難讀了很多,先從大神Dean Edward的addEvent開始入手吧,地址在這裡。源碼不長
function addEvent(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else{
if(!handler.$$guid) handler.$$guid = addEvent.guid++;
if(!element.events) element.events = {};
var handlers = element.events[type];
if(!handlers){
handlers = element.events[type] = {};
if(element["on"+type]){
handlers[0] = element["on"+type];
}
}
handlers[handler.$$guid] = handler;
element["on"+type]=handleEvent;
}
}
作為主要的addEvent()部分,直接看不支持addEvenetListener的地方,可以看出,其對於事件句柄,handler作了處理,新增了$$guid的屬性,在remove的時候會很方便。同時,在函數內部,以handlers來簡化位元組,實質上仍然操作的是element.events,然後使用"onXXX"的方式,來傳遞handleEvent()綁定事件。
在handleEvent中,
function handleEvent(event) {
var returnValue = true;
event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
var handlers = this.events[event.type];
for (var i in handlers) {
this.$$handleEvent = handlers[i];
// 執行
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
return returnValue;
};
可以看到,把addEvent中處理過的events進行使用,這裡的this因為在addEvent()中使用了element["on"+type]=handleEvent
,所以this在觸發操作時變為element,當然,還有修複對象的fixEvent。
在對addEvent的評論中,建議元素分配一個UUID,所有回調放到一個對象中存儲,也就是把events給拋開,而使用緩存來存儲對應元素的回調列表。
jQuery的event
如果和上一篇JavaScript事件詳解-zepto的事件實現(二)相比較而言,在3.1.1版本中,入口推薦用on:
$("#btn").on("click",function(event){
console.log(event);
});
可以看到event對象已經發生了變化,相對於zepto生成的一個新的事件對象而言,jQuery的event對象是重新修改的一個內部對象。
入口
跟事件綁定有關的入口,可以看出,bind和delegate內部仍然是用的on方法。
如果按上述的例子,那麼在給$("#btn")註冊click事件時,會通過jQuery.fn.on方法,然後調用jQuery內部的on()函數,
function on(elem,types,selector,data,fn,one){...}
elem參數不用說,在調用on()函數時,傳了this過去,也就是$("#btn"),對外部開放的介面里,只有四個參數types,selector,data,fn。也因此,先從這四個參數入手:
- types,不如說event types更恰當,也就是上面例子中傳的'click',可以用空格來分隔,一次傳入多個事件類型
- selector,用於事件委托的選擇符參數
- data,當一個事件被觸發時要傳遞的data給事件處理函數,在回調的event中會有該屬性,以便於使用
- fn,回調,事件句柄
在on()函數里,首先分層:
可以看到,省略的幾個判斷中,都是對於這些參數做的處理,第一個省略處是對於types的處理,其中對如果types是map類型的值做了處理,再次開始on()函數。
第二個省略處則是對於參數的簡略使用,在代碼中,可以看到其註釋展示的三種情況:
// (types,fn)
// (types,selector,fn)
// (types,data,fn)
也相當於在使用時,我們可以略去data和selector來簡單的完成一次綁定。
而第三處則是對one的判斷,也就是在回調中加入了off(),即調用一次回調,立刻off event。
on()只是對於參數的處理,接下來就是使用jQuery.event.add()來再次進行處理。
如果說jQuery.Event是對event對象的校正,那麼jQuery.event則是提供了內部方法:
首先是add(elem,types,handler,data,selector):
為什麼add才是開始監聽,因為只有在這裡才能找到addEventListener,DOM0級的onXXX,和DOM2級的addEventListener,IE的attachEvent,來作為切入點。
在函數的初始,首先開始獲取elemData=dataPriv.get(elem)
,從字面意思看,這個應該是內部的緩存,也就是上面對於addEvent()所提到的緩存的回調列表。
作為緩存系統,確實有獨到之處。
cache
本來應該重開一章的,但是重點還是應該放到事件處理上,所以只是結合jQuery 2.0.3 源碼分析 數據緩存來說下自己的理解,以及相應的佐證。
首先就是內部的Data()函數,
(3.1.1版本)
這裡確實不應該,最近一段時間任務蠻多的,也沒有仔細讀源碼,看了一些文章後,先入為主的去找全局緩存去了,找啊找,還是too young too simple。對比一下也知道有問題撒。
(2.1.1版本)
才發現3.1.1版本中的Data已經沒有了cache,之前會將事件存入cache中,只給每個dom節點一個uid來當作鑰匙,獲取數據,雖然兩個版本大體結構都是創建了一個Data類,具有get,set方法,果然好久不打java,把面向對象全還給老師了,但不同的是3.1.1版本中則直接使用:
將其存入dom節點之中,這裡看來需要深入理解下了,為什麼3.0版本會把數據存入節點中?
我們再回到上文,繼續add()。
jQuery.event.add
現在就可以知道這個elemData從何處獲取到事件。
初始狀態第一次添加綁定時,所獲取到的肯定是個空對象,而第二次再次綁定就可以拿到第一次綁定的行為的值,註意傳的參數是elem。
然後除了對selector進行處理之外,還給事件句柄添加唯一的guid,看到上圖,也知道在之後的處理中,會給elemData這個對象增加兩個新的鍵:events和handle。
其中handle
可以看到這裡修改事件句柄,所以addEvenetListener時,回調會從jQuery.event.dispatch中來觸發。
這裡的handlers僅僅是內部對象,用來建立內部隊列。
add()函數剩下的則是根據參數的types的長度(“分隔符為空格”)來修改elemData.events的值,並且在事件初次綁定時,執行addEventListener()
這裡有個special
可以看到這個對象中存入的其實是一些特殊的事件,每個事件都會有一些定義的屬性,用於綁定,或判斷.
if(!special.setup || special.setup.call(elem,data,namespaces,eventHandle) === false){
if(elem.addEventListener){
elem.addEventListener(type,eventHandle);
}
}
進行綁定。
- special.setup:初始化綁定
- specia.delegateType : speacial.bindType:事件類型,在handleObj中會傳入保存到dom節點里。
- trigger:內部觸發時使用的事件句柄,
- _default:預設操作相容
- handle:
只有這四個事件會有handle,同樣是觸發時的事件句柄 - noBubble:防止image.load事件冒泡到window.load事件
- preDispatch:在dispatch中截斷,來執行該事件句柄
- postDispatch:在dispatch內部的事件隊列執行完畢之後,來執行該事件句柄
- add:添加事件綁定
- remove:解除事件綁定
之後就是handleObj
這個內部的對象保存了之後存入events中相應事件的值
可以看到,這裡使用了jQuery的extend來擴展,可能存在的handleObjIn作為補充對象。
開始調用原生addEventListener進行監聽
大部分還是會走原生事件監聽方法,這裡的handlers很有意思,不同於zepto使用handles作為內部隊列,因為jQuery有緩存機制。
handlers = events[type] = [];
handlers.delegateCount = 0;
從這裡可以得出在add內部,handlers引用的是events[type],並且這個指針有一個delegateCount
的屬性,而在add的最後部分
將其按順序推入處理列表中。因為只有滿足selector
存在的情況下,delegateCount才會開始增加,所以之後的handlers函數中可以看到相應的處理。
jQuery.event.global[type] = true
這裡倒是不知道什麼意思,不過至少記錄了所有的監聽事件名稱
jQuery.event.dispatch
remove其實和觸發的事件流沒有什麼關係,所以還是以事件觸發流程開始分析。
上文的add中,將事件句柄做了一次處理:
return typeof jQuery !== "undefined" && jQuery.event,triggered !== e.type ? jQuery.event.dispatch.apply(elem,arguments)
可以看到,dispatch一開始就會使用jQuery.event.fix(nativeEvent)
來進行event對象修正。
jQuery.event.fix 或者更直接是jQuery.Event()
可以看到,處理之後event就變為jQuery.event,fix只是檢查相應對象上,是否有緩存對象,否則就新建一個Event類的實例。
不過3.1.1版本和2.1.1有點區別:
this.target = (src.target && src.target.nodeType === 3) ? src.target.parentNode : src.target;
this.currentTarget = src.currentTarget;
this.relatedTarget = src.relatedTarget;
修複文字不應該成為觸發節點。
新增的jQuery.event.addProp()方法
可以看到,3.1版的event對外提供了event對象上所有屬性的getter,setter方法
而在jQuery.Event的原型鏈中,其constructor被指回給了自己
還有相應的阻止冒泡,阻止預設動作的方法
其中simulate在8400多行,為focus(in | out)
提供觸發。
而isSimulated則處理這兩個方法的阻止動作。
重新回到dispatch方法,校準過event之後,在內部定義的變數中,我們可以看到
這裡又會去拿elem緩存中對應的事件,這裡就是之前add時存入elem節點中的數據。
然後使用jQuery.event.handlers來組建事件隊列,
先看事件隊列處理完之後
其中:
ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem,args);
會執行回調函數。
dispatch中簡單的邏輯說完了,如果以簡單的綁定事件而言,已能夠完成功能。但如果僅止於此,那麼自然對不起jQuery事件模塊的那麼多行代碼。
從全局角度來看jQuery.event
首先,用戶通過註冊jQuery.on方法開始生成事件,然後是add中dataPriv.get(elem)
獲取或設置緩存
並對handler做處理,並設置唯一的guid,在觸發事件dispatch中,首先使用fix方法校準事件,然後生成事件隊列。逐個開始執行
jQuery.event.handlers
可以看到,如果delegeteCount為0,也就是沒有委托,中間的處理則直接略過,其中的cur=event.target
,即觸發動作的節點,而在下麵,很明顯的cur又被指成了elem,返回的handlerQueue其實只是簡單的將傳入的參數做了個組裝。
事件委托
簡單的事件其實對於隊列的要求不高,只要返回之後執行就好,但事件委托則不是這樣,之前也介紹了zepto和原生事件里對於事件委托的處理,但jQuery的委托機制又格外不同。
在介紹jQuery.event.add()時的addEventListener(type,eventHandle)
,所傳的事件句柄
重新寫個事件委托的例子
$(".jumbotron").on("click","#test",function(e){
console.log("test");
}).on("click",function(e){
console.log("demo")
})
接著上面的handlers事件來講,當有委托元素,也就是selector不為空時,則進入判斷體之中:
if(delegateCount && cur.nodeType && !(event.type === "click" && event.button >=1 ))
防止火狐中右鍵或中鍵點擊時,會冒泡到document的click事件,
然後是
for(;cur !== this;cur = cur.parentNode || this)
cur會不斷的往上遍歷,去尋找綁定事件的節點,這裡通過迴圈來模仿冒泡機制,
然後查找節點,將回調函數加入到matchedSelectors之中,並返回經過驗證的事件隊列。
如果按照上面那個例子來說,點擊#test
元素時,所生成的事件隊列就是:
而在執行相關的事件時,也會從test
元素先開始觸發回調,然後是jumbotron
。
也因此,會發現其實都是一樣的思路,在委托元素上,都是判斷觸發的元素是否在綁定的元素之中,再由內而外的開始執行事件。
當然,在執行事件隊列時,也要註意,jQuery這裡的處理方法
能夠使得回調中的this就是selector的節點。
而各種處理相容性的special事件,則更需要對各個瀏覽器操作時的差異有足夠的理解。
小結
這次的閱讀不大順利,中間工作比較忙,自己也確實是比較懶,所以進展比較慢,像模擬事件都沒有想好怎麼寫,不過也確實讓自己多想想該怎麼歸納知識點,中間很多的思緒和時間都浪費掉了,看來還是單獨分小章節寫比較適合節奏。