javascript
JavaScript事件详解-jQuery的事件实现(三)
正文
本文所涉及到的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的時候會很方便。同時,在函數(shù)內(nèi)部,以handlers來簡化字節(jié),實質(zhì)上仍然操作的是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];// 執(zhí)行if (this.$$handleEvent(event) === false) {returnValue = false;}}return returnValue; };可以看到,把addEvent中處理過的events進行使用,這里的this因為在addEvent()中使用了element["on"+type]=handleEvent,所以this在觸發(fā)操作時變?yōu)閑lement,當然,還有修復對象的fixEvent。
在對addEvent的評論中,建議元素分配一個UUID,所有回調(diào)放到一個對象中存儲,也就是把events給拋開,而使用緩存來存儲對應元素的回調(diào)列表。
jQuery的event
如果和上一篇JavaScript事件詳解-zepto的事件實現(xiàn)(二)相比較而言,在3.1.1版本中,入口推薦用on:
$("#btn").on("click",function(event){console.log(event); });
可以看到event對象已經(jīng)發(fā)生了變化,相對于zepto生成的一個新的事件對象而言,jQuery的event對象是重新修改的一個內(nèi)部對象。
入口
跟事件綁定有關(guān)的入口,可以看出,bind和delegate內(nèi)部仍然是用的on方法。
如果按上述的例子,那么在給$("#btn")注冊click事件時,會通過jQuery.fn.on方法,然后調(diào)用jQuery內(nèi)部的on()函數(shù),
function on(elem,types,selector,data,fn,one){...}
elem參數(shù)不用說,在調(diào)用on()函數(shù)時,傳了this過去,也就是$("#btn"),對外部開放的接口里,只有四個參數(shù)types,selector,data,fn。也因此,先從這四個參數(shù)入手:
- types,不如說event types更恰當,也就是上面例子中傳的'click',可以用空格來分隔,一次傳入多個事件類型
- selector,用于事件委托的選擇符參數(shù)
- data,當一個事件被觸發(fā)時要傳遞的data給事件處理函數(shù),在回調(diào)的event中會有該屬性,以便于使用
- fn,回調(diào),事件句柄
在on()函數(shù)里,首先分層:
可以看到,省略的幾個判斷中,都是對于這些參數(shù)做的處理,第一個省略處是對于types的處理,其中對如果types是map類型的值做了處理,再次開始on()函數(shù)。
第二個省略處則是對于參數(shù)的簡略使用,在代碼中,可以看到其注釋展示的三種情況:
也相當于在使用時,我們可以略去data和selector來簡單的完成一次綁定。
而第三處則是對one的判斷,也就是在回調(diào)中加入了off(),即調(diào)用一次回調(diào),立刻off event。
on()只是對于參數(shù)的處理,接下來就是使用jQuery.event.add()來再次進行處理。
如果說jQuery.Event是對event對象的校正,那么jQuery.event則是提供了內(nèi)部方法:
首先是add(elem,types,handler,data,selector):
為什么add才是開始監(jiān)聽,因為只有在這里才能找到addEventListener,DOM0級的onXXX,和DOM2級的addEventListener,IE的attachEvent,來作為切入點。
在函數(shù)的初始,首先開始獲取elemData=dataPriv.get(elem),從字面意思看,這個應該是內(nèi)部的緩存,也就是上面對于addEvent()所提到的緩存的回調(diào)列表。
作為緩存系統(tǒng),確實有獨到之處。
cache
本來應該重開一章的,但是重點還是應該放到事件處理上,所以只是結(jié)合jQuery 2.0.3 源碼分析 數(shù)據(jù)緩存來說下自己的理解,以及相應的佐證。
首先就是內(nèi)部的Data()函數(shù),
(3.1.1版本)
這里確實不應該,最近一段時間任務蠻多的,也沒有仔細讀源碼,看了一些文章后,先入為主的去找全局緩存去了,找啊找,還是too young too simple。對比一下也知道有問題撒。
(2.1.1版本)
才發(fā)現(xiàn)3.1.1版本中的Data已經(jīng)沒有了cache,之前會將事件存入cache中,只給每個dom節(jié)點一個uid來當作鑰匙,獲取數(shù)據(jù),雖然兩個版本大體結(jié)構(gòu)都是創(chuàng)建了一個Data類,具有g(shù)et,set方法,果然好久不打java,把面向?qū)ο笕€給老師了,但不同的是3.1.1版本中則直接使用:
將其存入dom節(jié)點之中,這里看來需要深入理解下了,為什么3.0版本會把數(shù)據(jù)存入節(jié)點中?
我們再回到上文,繼續(xù)add()。
jQuery.event.add
現(xiàn)在就可以知道這個elemData從何處獲取到事件。
初始狀態(tài)第一次添加綁定時,所獲取到的肯定是個空對象,而第二次再次綁定就可以拿到第一次綁定的行為的值,注意傳的參數(shù)是elem。
然后除了對selector進行處理之外,還給事件句柄添加唯一的guid,看到上圖,也知道在之后的處理中,會給elemData這個對象增加兩個新的鍵:events和handle。
其中handle
可以看到這里修改事件句柄,所以addEvenetListener時,回調(diào)會從jQuery.event.dispatch中來觸發(fā)。
這里的handlers僅僅是內(nèi)部對象,用來建立內(nèi)部隊列。
add()函數(shù)剩下的則是根據(jù)參數(shù)的types的長度(“分隔符為空格”)來修改elemData.events的值,并且在事件初次綁定時,執(zhí)行addEventListener()
這里有個special
可以看到這個對象中存入的其實是一些特殊的事件,每個事件都會有一些定義的屬性,用于綁定,或判斷.
if(!special.setup || special.setup.call(elem,data,namespaces,eventHandle) === false){if(elem.addEventListener){elem.addEventListener(type,eventHandle);} }進行綁定。
只有這四個事件會有handle,同樣是觸發(fā)時的事件句柄
之后就是handleObj
這個內(nèi)部的對象保存了之后存入events中相應事件的值
可以看到,這里使用了jQuery的extend來擴展,可能存在的handleObjIn作為補充對象。
開始調(diào)用原生addEventListener進行監(jiān)聽
大部分還是會走原生事件監(jiān)聽方法,這里的handlers很有意思,不同于zepto使用handles作為內(nèi)部隊列,因為jQuery有緩存機制。
從這里可以得出在add內(nèi)部,handlers引用的是events[type],并且這個指針有一個delegateCount的屬性,而在add的最后部分
將其按順序推入處理列表中。因為只有滿足selector存在的情況下,delegateCount才會開始增加,所以之后的handlers函數(shù)中可以看到相應的處理。
這里倒是不知道什么意思,不過至少記錄了所有的監(jiān)聽事件名稱
jQuery.event.dispatch
remove其實和觸發(fā)的事件流沒有什么關(guān)系,所以還是以事件觸發(fā)流程開始分析。
上文的add中,將事件句柄做了一次處理:
可以看到,dispatch一開始就會使用jQuery.event.fix(nativeEvent)來進行event對象修正。
jQuery.event.fix 或者更直接是jQuery.Event()
可以看到,處理之后event就變?yōu)閖Query.event,fix只是檢查相應對象上,是否有緩存對象,否則就新建一個Event類的實例。
不過3.1.1版本和2.1.1有點區(qū)別:
修復文字不應該成為觸發(fā)節(jié)點。
新增的jQuery.event.addProp()方法
可以看到,3.1版的event對外提供了event對象上所有屬性的getter,setter方法
而在jQuery.Event的原型鏈中,其constructor被指回給了自己
還有相應的阻止冒泡,阻止默認動作的方法
其中simulate在8400多行,為focus(in | out)提供觸發(fā)。
而isSimulated則處理這兩個方法的阻止動作。
重新回到dispatch方法,校準過event之后,在內(nèi)部定義的變量中,我們可以看到
這里又會去拿elem緩存中對應的事件,這里就是之前add時存入elem節(jié)點中的數(shù)據(jù)。
然后使用jQuery.event.handlers來組建事件隊列,
先看事件隊列處理完之后
其中:
會執(zhí)行回調(diào)函數(shù)。
dispatch中簡單的邏輯說完了,如果以簡單的綁定事件而言,已能夠完成功能。但如果僅止于此,那么自然對不起jQuery事件模塊的那么多行代碼。
從全局角度來看jQuery.event
首先,用戶通過注冊jQuery.on方法開始生成事件,然后是add中dataPriv.get(elem)獲取或設置緩存
并對handler做處理,并設置唯一的guid,在觸發(fā)事件dispatch中,首先使用fix方法校準事件,然后生成事件隊列。逐個開始執(zhí)行
jQuery.event.handlers
可以看到,如果delegeteCount為0,也就是沒有委托,中間的處理則直接略過,其中的cur=event.target,即觸發(fā)動作的節(jié)點,而在下面,很明顯的cur又被指成了elem,返回的handlerQueue其實只是簡單的將傳入的參數(shù)做了個組裝。
事件委托
簡單的事件其實對于隊列的要求不高,只要返回之后執(zhí)行就好,但事件委托則不是這樣,之前也介紹了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事件,
然后是
cur會不斷的往上遍歷,去尋找綁定事件的節(jié)點,這里通過循環(huán)來模仿冒泡機制,
然后查找節(jié)點,將回調(diào)函數(shù)加入到matchedSelectors之中,并返回經(jīng)過驗證的事件隊列。
如果按照上面那個例子來說,點擊#test元素時,所生成的事件隊列就是:
而在執(zhí)行相關(guān)的事件時,也會從test元素先開始觸發(fā)回調(diào),然后是jumbotron。
也因此,會發(fā)現(xiàn)其實都是一樣的思路,在委托元素上,都是判斷觸發(fā)的元素是否在綁定的元素之中,再由內(nèi)而外的開始執(zhí)行事件。
當然,在執(zhí)行事件隊列時,也要注意,jQuery這里的處理方法
能夠使得回調(diào)中的this就是selector的節(jié)點。
而各種處理兼容性的special事件,則更需要對各個瀏覽器操作時的差異有足夠的理解。
小結(jié)
這次的閱讀不大順利,中間工作比較忙,自己也確實是比較懶,所以進展比較慢,像模擬事件都沒有想好怎么寫,不過也確實讓自己多想想該怎么歸納知識點,中間很多的思緒和時間都浪費掉了,看來還是單獨分小章節(jié)寫比較適合節(jié)奏。
轉(zhuǎn)載于:https://www.cnblogs.com/leomYili/p/6215021.html
總結(jié)
以上是生活随笔為你收集整理的JavaScript事件详解-jQuery的事件实现(三)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ionic实战 自动升级APP(Andr
- 下一篇: CentOS7安装bbr的正确姿势:更新