生活随笔
收集整理的這篇文章主要介紹了
jQuery源码分析--Event模块(1)
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
jQuery的Event模塊提供了強(qiáng)大的功能:事件代理,自定義事件,自定義數(shù)據(jù)等。今天記錄一下它實(shí)現(xiàn)的原理。
我們都知道,在js的原生事件中,有事件對(duì)象和回調(diào)函數(shù)這兩樣?xùn)|西。但是事件對(duì)象是只讀的,所以jQuery就用了自己的Event對(duì)象替代了原生的事件對(duì)象,這樣就可以實(shí)現(xiàn)對(duì)事件對(duì)象的完全控制,所以才能實(shí)現(xiàn)自定義數(shù)據(jù)。而回調(diào)函數(shù)的話,每個(gè)元素只有一個(gè)一樣的回調(diào)函數(shù),這樣方便管理。
下面來看看event對(duì)象長(zhǎng)什么樣。
可以看到j(luò)Query的事件對(duì)象其實(shí)一開始就只有這么一點(diǎn)東西。
其中originalEvent是原生事件對(duì)象副本。
jQuery211030632698768749833則是一個(gè)標(biāo)志,以后可以用這個(gè)標(biāo)志來判斷這個(gè)對(duì)象是不是jQuery的事件對(duì)象。
緊接著我們看一下Event對(duì)象的原型。
可以看到有六個(gè)個(gè)方法,前三個(gè)是用來判斷是否已經(jīng)被阻止默認(rèn)行為、是否已經(jīng)被阻止冒泡和默認(rèn)行為、是否已經(jīng)被阻止冒泡。
后三個(gè)則是相應(yīng)的操作。
上源代碼: jQuery.Event = function( src, props ) {//src可以是原生事件類型、jquery事件類型、自定義事件類型、原生事件對(duì)象// Allow instantiation without the 'new' keyword 不用new關(guān)鍵字實(shí)例化jquery的事件對(duì)象if ( !(this instanceof jQuery.Event) ) {return new jQuery.Event( src, props );}// Event objectif ( src && src.type ) {//如果是原生事件對(duì)象或jquery事件類型this.originalEvent = src;//保存原生事件對(duì)象 this.type = src.type;//事件類型// Events bubbling up the document may have been marked as prevented// by a handler lower down the tree; reflect the correct value.this.isDefaultPrevented = src.defaultPrevented ||//是否被更底層的事件阻止默認(rèn)行為src.defaultPrevented === undefined &&// Support: Android < 4.0src.returnValue === false ?returnTrue :returnFalse;// Event type} else {//原生事件類型、自定義事件類型this.type = src;}// Put explicitly provided properties onto the event objectif ( props ) {//如果傳入了自定義的props對(duì)象,將其復(fù)制到j(luò)Query.Event對(duì)象上jQuery.extend( this, props );}// Create a timestamp if incoming event doesn't have onethis.timeStamp = src && src.timeStamp || jQuery.now();//加時(shí)間戳 // Mark it as fixedthis[ jQuery.expando ] = true;//jQuery.expando是頁面中每一個(gè)jQuery副本唯一的標(biāo)志。此屬性來判斷當(dāng)前事件對(duì)象是否為jQuery事件對(duì)象
};// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {isDefaultPrevented: returnFalse,//是否已經(jīng)阻止默認(rèn)行為isPropagationStopped: returnFalse,//是否已經(jīng)阻止事件傳播isImmediatePropagationStopped: returnFalse,//是否已經(jīng)阻止事件執(zhí)行和事件傳播
preventDefault: function() {var e = this.originalEvent;this.isDefaultPrevented = returnTrue;if ( e && e.preventDefault ) {e.preventDefault();}},stopPropagation: function() {var e = this.originalEvent;this.isPropagationStopped = returnTrue;if ( e && e.stopPropagation ) {e.stopPropagation();}},stopImmediatePropagation: function() {var e = this.originalEvent;this.isImmediatePropagationStopped = returnTrue;if ( e && e.stopImmediatePropagation ) {e.stopImmediatePropagation();}this.stopPropagation();}
}; 可以看到,jQuery用構(gòu)造函數(shù)來創(chuàng)建對(duì)象,并且用prototype原型來繼承公有的方法。
但是jQuery事件對(duì)象還沒就此就結(jié)束了。因?yàn)檫€需要把像target這些有用的事件屬性從原生的事件對(duì)象復(fù)制過來。這就是工具方法jQuery.event.fix()的作用了。
看一下經(jīng)過fix函數(shù)之后Event對(duì)象變成了什么樣子。
可以看到,應(yīng)該有的屬性都有了。這里的fix還做了一些兼容性的事情。
在鍵盤事件的時(shí)候,只是按下按鍵按鈕的keycode和charcode在不同瀏覽器下的表現(xiàn)不一樣。所以jQuery統(tǒng)一用一個(gè)which屬性來指示。
另外在鼠標(biāo)事件的時(shí)候,因?yàn)橛袀€(gè)button屬性,該屬性是記錄按下鼠標(biāo)按鈕的。但是ie和dom標(biāo)準(zhǔn)不一樣。統(tǒng)一把它修正,并用which來記錄。
還有一個(gè)就ie低版本不支持pageX和pageY的情況。
上代碼:
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks: {},keyHooks: {props: "char charCode key keyCode".split(" "),filter: function( event, original ) {// Add which for key eventsif ( event.which == null ) {event.which = original.charCode != null ? original.charCode : original.keyCode;}return event;}},mouseHooks: {props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter: function( event, original ) {var eventDoc, doc, body,button = original.button;// Calculate pageX/Y if missing and clientX/Y availableif ( event.pageX == null && original.clientX != null ) {eventDoc = event.target.ownerDocument || document;doc = eventDoc.documentElement;body = eventDoc.body;event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );}// Add which for click: 1 === left; 2 === middle; 3 === right// Note: button is not normalized, so don't use itif ( !event.which && button !== undefined ) {event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );}return event;}},fix: function( event ) {//event可以為jQuery對(duì)象或者原生事件對(duì)象 復(fù)制事件對(duì)象屬性,并修正特殊的if ( event[ jQuery.expando ] ) {//判斷是否為jQuery事件對(duì)象return event;}// Create a writable copy of the event object and normalize some propertiesvar i, prop, copy,type = event.type,originalEvent = event,fixHook = this.fixHooks[ type ];//用于存放鍵盤和鼠標(biāo)事件的不兼容屬性,fixhooks初始值為空對(duì)象if ( !fixHook ) {//rkeyEvent = /^key/,rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,this.fixHooks[ type ] = fixHook =rmouseEvent.test( type ) ? this.mouseHooks :rkeyEvent.test( type ) ? this.keyHooks :{};}copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;//存放所有屬性的副本
event = new jQuery.Event( originalEvent );//創(chuàng)建jQuery事件對(duì)象
i = copy.length;while ( i-- ) {//把原生屬性和修正后的不兼容的屬性復(fù)制到j(luò)Query事件對(duì)象中prop = copy[ i ];event[ prop ] = originalEvent[ prop ];}// Support: Cordova 2.5 (WebKit) (#13255)// All events should have a target; Cordova deviceready doesn'tif ( !event.target ) {event.target = document;}// Support: Safari 6.0+, Chrome < 28// Target should not be a text node (#504, #13143)if ( event.target.nodeType === 3 ) {//修正Safari 6.0+, Chrome < 28中event.target為文本節(jié)點(diǎn)的情況event.target = event.target.parentNode;}return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;//修正鼠標(biāo)事件和鍵盤事件的專屬屬性;鍵盤的按鍵所對(duì)應(yīng)的編碼,和鼠標(biāo)的clientX、鼠標(biāo)編碼}, ?
接下來是處理函數(shù)了。每個(gè)元素只有一個(gè)處理函數(shù)。可以說就是dispatch()。為了配合這個(gè)處理函數(shù)的工作,還有一個(gè)對(duì)象,這個(gè)對(duì)象的每個(gè)元素是一個(gè)存放不同類型事件處理函數(shù)的數(shù)組。這個(gè)數(shù)組中存放著所有的代理事件和自身的事件。先來看看jQuery是如何把處理函數(shù)放進(jìn)這個(gè)數(shù)組的。jQuery中快捷事件函數(shù)click這一些會(huì)調(diào)用on函數(shù),而on函數(shù)又會(huì)調(diào)用工具函數(shù)jQuery.event.add()來綁定事件。所以在jQuery中所有的事件都是通過這個(gè)add函數(shù)來綁定的。
下面說說add函數(shù)。
add函數(shù)主要是把處理函數(shù)處理成一個(gè)對(duì)象,并把這個(gè)對(duì)象推入到處理函數(shù)對(duì)象數(shù)組的合適位置。
這個(gè)對(duì)象長(zhǎng)這個(gè)樣子:
其中,data是我們自定義的數(shù)據(jù)。
handler使我們傳進(jìn)去的處理函數(shù)
type是事件類型,
origType是我們傳進(jìn)去的事件類型,
selector是事件代理的選擇器。
在這里為什么會(huì)有個(gè)type和一個(gè)origType呢?這是因?yàn)橛行┦录愋筒缓每刂?#xff0c;所以就會(huì)拿別的事件類型來代替和模擬。這些事件有:
focus/blur因?yàn)椴恢С质录芭?#xff0c;所以會(huì)用focusein/focusout來代替。 mouseenter: "mouseover",
mouseleave: "mouseout",
pointerenter: "pointerover",
pointerleave: "pointerout" ? 在這些中,前面的會(huì)用后面的來代替。因?yàn)樵谟筛冈剡M(jìn)入子元素時(shí)重復(fù)觸發(fā)事件的問題。
那這些處理函數(shù)對(duì)象入數(shù)組有個(gè)什么樣的順序呢。其實(shí)就是先來的在前,后來的在后,代理事件在前。
看下面例子:
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>Event fun</title>
</head>
<body><div style="width:200px;height:600px;background-color: red;"><div style="width:200px;height:500px;background-color: blue;"><div style="width:200px;height:400px;background-color: green;"><div id="a" style="width:200px;height:300px;background-color: black;"><div style="width:200px;height:200px;background-color: yellow;"></div></div></div></div></div><script src="../../jquery-2.1.1.js"></script><script>$(document).on('click',function(){})$(document).on('click','#a',function(){})$(document).on('click','div',{name:'qq',age:'dd'},function(){console.log(1);})var doc = $(document)console.log(document.events.click)// document.onclick = function(e){// console.log(1);// }</script></body>
</html> 生成的數(shù)組對(duì)象如下圖。
上代碼:
add: function( elem, types, handler, data, selector ) {//將回調(diào)函數(shù)插入響應(yīng)數(shù)組var handleObjIn, eventHandle, tmp,events, t, handleObj,special, handlers, type, namespaces, origType,elemData = data_priv.get( elem );// Don't attach events to noData or text/comment nodes (but allow plain objects)if ( !elemData ) {//當(dāng)前元素不支持附加擴(kuò)展屬性return;}// Caller can pass in an object of custom data in lieu of the handlerif ( handler.handler ) {//自定義監(jiān)聽對(duì)象的情況handleObjIn = handler;handler = handleObjIn.handler;selector = handleObjIn.selector;}// Make sure that the handler has a unique ID, used to find/remove it laterif ( !handler.guid ) {//確定有唯一的idhandler.guid = jQuery.guid++;}// Init the element's event structure and main handler, if this is the firstif ( !(events = elemData.events) ) {//如果事件緩存對(duì)象不存在,則初始化.用于存儲(chǔ)事件對(duì)象events = elemData.events = {};}if ( !(eventHandle = elemData.handle) ) {//取出或初始化主監(jiān)聽函數(shù)eventHandle = elemData.handle = function( e ) {//丟棄jQuery.event.trigger()第二個(gè)事件和頁面關(guān)閉后觸發(fā)的事件// Discard the second event of a jQuery.event.trigger() and// when an event is called after a page has unloadedreturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?jQuery.event.dispatch.apply( elem, arguments ) : undefined;};}// Handle multiple events separated by a spacetypes = ( types || "" ).match( rnotwhite ) || [ "" ];//分解多事件t = types.length;while ( t-- ) {tmp = rtypenamespace.exec( types[t] ) || [];//分解事件type = origType = tmp[1];//單個(gè)事件類型namespaces = ( tmp[2] || "" ).split( "." ).sort();//分解命名空間數(shù)組// There *must* be a type, no attaching namespace-only handlersif ( !type ) {//忽略只有命名空間的情況continue;}// If event changes its type, use the special event handlers for the changed typespecial = jQuery.event.special[ type ] || {};//嘗試獲取當(dāng)前事件類型對(duì)應(yīng)的修正對(duì)象// If selector defined, determine special event api type, otherwise given typetype = ( selector ? special.delegateType : special.bindType ) || type;//修正type,如果有selector修正為代理事件,或者支持更好的類型// Update special based on newly reset typespecial = jQuery.event.special[ type ] || {};//type可能已經(jīng)改變,所以嘗試再次獲取修正對(duì)象// handleObj is passed to all event handlershandleObj = jQuery.extend({//把監(jiān)聽函數(shù)封裝成監(jiān)聽對(duì)象type: type,//修正后的事件類型origType: origType,//單個(gè)原始事件類型data: data,//傳入的附加對(duì)象handler: handler,//監(jiān)聽函數(shù)guid: handler.guid,//函數(shù)idselector: selector,//代理綁定時(shí)的過濾選擇器needsContext: selector && jQuery.expr.match.needsContext.test( selector ),namespace: namespaces.join(".")//原始命名空間
}, handleObjIn );// Init the event handler queue if we're the firstif ( !(handlers = events[ type ]) ) {//第一次綁定該類型事件時(shí),初始化監(jiān)聽對(duì)象數(shù)組handlers = events[ type ] = [];handlers.delegateCount = 0;//下一個(gè)代理監(jiān)聽對(duì)象的插入位置// Only use addEventListener if the special events handler returns falseif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {//優(yōu)先使用修正對(duì)象的修正方法綁定主監(jiān)聽函數(shù)if ( elem.addEventListener ) {elem.addEventListener( type, eventHandle, false );}}}//將監(jiān)聽對(duì)象插入對(duì)象數(shù)組if ( special.add ) {//修正對(duì)象有修正方法add,用add
special.add.call( elem, handleObj );if ( !handleObj.handler.guid ) {handleObj.handler.guid = handler.guid;}}// Add to the element's handler list, delegates in frontif ( selector ) {//將代理監(jiān)聽對(duì)象插入到指定位置handlers.splice( handlers.delegateCount++, 0, handleObj );} else {//非代理的插入末尾
handlers.push( handleObj );}// Keep track of which events have ever been used, for event optimizationjQuery.event.global[ type ] = true;//記錄綁定過的事件類型
}},//修正事件的代碼。
// Create mouseenter/leave events using mouseover/out and event-time checks
// Support: Chrome 15+
jQuery.each({//修正這四個(gè)事件的處理函數(shù)mouseenter: "mouseover",mouseleave: "mouseout",pointerenter: "pointerover",pointerleave: "pointerout"
}, function( orig, fix ) {jQuery.event.special[ orig ] = {delegateType: fix,bindType: fix,handle: function( event ) {var ret,target = this,related = event.relatedTarget,handleObj = event.handleObj;// For mousenter/leave call the handler if related is outside the target.// NB: No relatedTarget if the mouse left/entered the browser windowif ( !related || (related !== target && !jQuery.contains( target, related )) ) {event.type = handleObj.origType;ret = handleObj.handler.apply( this, arguments );event.type = fix;}return ret;}};
});// Create "bubbling" focus and blur events
// Support: Firefox, Chrome, Safari
if ( !support.focusinBubbles ) {//修正focus/blur的處理函數(shù)。和特殊的主監(jiān)聽函數(shù)的添加和刪除(因?yàn)椴恢С置芭?#xff09;jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {// Attach a single capturing handler on the document while someone wants focusin/focusoutvar handler = function( event ) {jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );};jQuery.event.special[ fix ] = {setup: function() {var doc = this.ownerDocument || this,attaches = data_priv.access( doc, fix );if ( !attaches ) {doc.addEventListener( orig, handler, true );}data_priv.access( doc, fix, ( attaches || 0 ) + 1 );},teardown: function() {var doc = this.ownerDocument || this,attaches = data_priv.access( doc, fix ) - 1;if ( !attaches ) {doc.removeEventListener( orig, handler, true );data_priv.remove( doc, fix );} else {data_priv.access( doc, fix, attaches );}}};});
} ?
轉(zhuǎn)載于:https://www.cnblogs.com/dq-Leung/p/4339652.html
總結(jié)
以上是生活随笔為你收集整理的jQuery源码分析--Event模块(1)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。