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