提及jQuery的事件,不得不提一下Dean Edwards大神 addEvent庫,不少流行的類庫的基本思想從他那兒借來的web
jQuery的事件處理機制吸收了JavaScript專家Dean Edwards編寫的事件處理函數的精華,使得jQuery處理事件綁定的時候至關的可靠。編程
在預留退路(graceful degradation),按部就班以及非入侵式編程思想方面,jQuery也作的很是不錯api
事件的流程圖數組
總的來講對於JQuery的事件綁定瀏覽器
在綁定的時候作了包裝處理緩存
在執行的時候有過濾器處理app
.on( events [, selector ] [, data ], handler(eventObject) )函數
events:事件名post
selector : 一個選擇器字符串,用於過濾出被選中的元素中能觸發事件的後代元素優化
data :當一個事件被觸發時,要傳遞給事件處理函數的
handler:事件被觸發時,執行的函數
例如:
var body = $('body') body.on('click','p',function(){ console.log(this) })
用on方法給body上綁定一個click事件,冒泡到p元素的時候纔出發回調函數
這裏你們須要明確一點:每次在body上點擊其實都會觸發事件,可是隻目標爲p元素的狀況下才會觸發回調handler
經過源碼不難發現,on方法實質只完成一些參數調整的工做,而實際負責事件綁定的是其內部jQuery.event.add方法
jQuery.on
針對事件處理,咱們能夠拆分2部分:
一個事件預綁按期
一個事件執行期
本章着重講解事件的預綁定的時候作了那些處理,爲何要這樣處理?
事件底層的綁定接口無非就是用addEventListener處理的,因此咱們直接定位到addEventListener下面
jQuery.event.add 中有
elem: 目標元素
type: 事件類型,如’click’
eventHandle: 事件句柄,也就是事件回調處理的內容了
false: 冒泡
如今咱們把以前的案例給套一下看看
var body = document.getElementsByTagName('body') var eventHandle = function(){ console.log(this) } body .addEventListener( ‘click’, eventHandle, false );
明顯有問題,每次在body上都觸發了回調,少了個p元素的處理,固然這樣的效果也沒法處理
eventHandle源碼
回到內部綁定的事件句柄eventHandle ,可想而知eventHandle不只僅只是隻是充當一個回調函數的角色,而是一個實現了EventListener接口的對象
if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events eventHandle.elem = elem; }
可見在eventHandle中並無直接處理回調函數,而是映射到jQuery.event.dispatch分派事件處理函數了
僅僅只是傳入eventHandle.elem,arguments , 就是body元素 與事件對象
那麼這裏有個問題,事件回調的句柄並無傳遞過去,後面的代碼如何關聯?
本章的一些地方可能要結合後面的dispatch處理才能理清,可是咱們仍是先看看作了那些處理
on內部的實現機制
咱們開從頭來理清下jQuery.event.add代碼結構,適當的跳過這個環節中不能理解的代碼,具體遇到在提出
以前就提到過jQuery從1.2.3版本引入數據緩存系統,貫穿內部,爲整個體系服務,事件體系也引入了這個緩存機制
因此jQuery並無將事件處理函數直接綁定到DOM元素上,而是經過$.data存儲在緩存$.cahce上
第一步:獲取數據緩存
//獲取數據緩存 elemData = data_priv.get( elem );
在$.cahce緩存中獲取存儲的事件句柄對象,若是沒就新建elemData
第二步:建立編號
if ( !handler.guid ) { handler.guid = jQuery.guid++; }
爲每個事件的句柄給一個標示,添加ID的目的是 用來尋找或者刪除handler,由於這個東東是緩存在緩存對象上的,沒有直接跟元素節點發生關聯
第三步:分解事件名與句柄
if ( !(events = elemData.events) ) { events = elemData.events= {}; } if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; eventHandle.elem = elem; }
events,eventHandle 都是elemData緩存對象內部的,可見
在elemData中有兩個重要的屬性,
一個是events,是jQuery內部維護的事件列隊
一個是handle,是實際綁定到elem中的事件處理函數
以後的代碼無非就是對這2個對象的篩選,分組,填充了
第四步: 填充事件名與事件句柄
// Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); // 事件多是經過空格鍵分隔的字符串,因此將其變成字符串數組 // core_rnotwhite:/\S+/g types = ( types || "" ).match( core_rnotwhite ) || [""]; // 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"] // 事件的個數 t = types.length; while ( t-- ) { // 嘗試取出事件的命名空間 // 如"mouseover.a.b" → ["mouseover.a.b", "mouseover", "a.b"] tmp = rtypenamespace.exec( types[t] ) || []; // 取出事件類型,如mouseover type = origType = tmp[1]; // 取出事件命名空間,如a.b,並根據"."分隔成數組 namespaces = ( tmp[2] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type // 事件是否會改變當前狀態,若是會則使用特殊事件 special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type // 根據是否已定義selector,決定使用哪一個特殊事件api,若是沒有非特殊事件,則用type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type // type狀態發生改變,從新定義特殊事件 special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers // 這裏把handleObj叫作事件處理對象,擴展一些來着handleObjIn的屬性 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn ); // Init the event handler queue if we're the first // 初始化事件處理列隊,若是是第一次使用,將執行語句 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false // 若是獲取特殊事件監聽方法失敗,則使用addEventListener進行添加事件 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } } } // 特殊事件使用add處理 if ( special.add ) { special.add.call( elem, handleObj ); // 設置事件處理函數的ID if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front // 將事件處理對象推入處理列表,姑且定義爲事件處理對象包 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization // 表示事件曾經使用過,用於事件優化 jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE // 設置爲null避免IE中循環引用致使的內存泄露 elem = null; },
這段比較長了分解下,最終的目的就是爲填充events,eventHandle
涉及
多事件處理:
若是是多事件分組的狀況jQuery(...).bind("mouseover mouseout", fn);
事件多是經過空格鍵分隔的字符串,因此將其變成字符串數組
增長命名空間處理:
事件名稱能夠添加指定的event namespaces(命名空間) 來簡化刪除或觸發事件。例如,"click.myPlugin.simple"
爲 click 事件同時定義了兩個命名空間 myPlugin 和 simple。經過上述方法綁定的 click 事件處理,能夠用.off("click.myPlugin")
或 .off("click.simple")
刪除綁定到相應元素的Click事件處理程序,而不會干擾其餘綁定在該元素上的「click(點擊)」 事件。命名空間相似CSS類,由於它們是不分層次的;只須要有一個名字相匹配便可。如下劃線開頭的名字空間是供 jQuery 使用的。
引入jQuery的Special Event機制
何時要用到自定義函數?有些瀏覽器並不兼容某類型的事件,如IE6~8不支持hashchange事件,你沒法經過jQuery(window).bind('hashchange', callback)來綁定這個事件,這個時候你就能夠經過jQuery自定義事件接口來模擬這個事件,作到跨瀏覽器兼容。
原理
jQuery(elem).bind(type, callbakc)其實是映射到 jQuery.event.add(elem, types, handler, data)這個方法,每個類型的事件會初始化一次事件處理器,而傳入的回調函數會以數組的方式緩存起來,當事件觸發的時候處理器將依次執行這個數組。
jQuery.event.add方法在第一次初始化處理器的時候會檢查是否爲自定義事件,若是存在則將會把控制權限交給自定義事件的事件初始化函數,一樣事件卸載的jQuery.event.remove方法在刪除處理器前也會檢查此。
!special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false jQuery.removeEvent( elem, type, elemData.handle );
jQuery.event.special對象中,保存着爲適配特定事件所需的變量和方法,
具體有:
delegateType / bindType (用於事件類型的調整)
setup (在某一種事件第一次綁定時調用)
add (在事件綁定時調用)
remove (在解除事件綁定時調用)
teardown (在全部事件綁定都被解除時調用)
trigger (在內部trigger事件的時候調用)
noBubble
_default
handle (在實際觸發事件時調用)
preDispatch (在實際觸發事件前調用)
postDispatch (在實際觸發事件後調用)
在適配工做完成時,會產生一個handleObj對象,這個對象包含了全部在事件實際被觸發是所需的全部參數
採用自定義事件或者瀏覽器接口綁定事件
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } }
冒泡標記
handlers.splice( handlers.delegateCount++, 0, handleObj );
最後記得
設置爲null避免IE中循環引用致使的內存泄露
elem = null;
這個元素沒有直接讓事件直接引用了,而是掛在到,數據緩存句柄上,很好的避免了這個IE泄露的問題
eventHandle.elem = elem;
經過整個流程,咱們的數據緩存對象就填充完畢了,看看截圖
events:handleObj
handle
數據緩存對象
得出總結:
在jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : 方法中沒有傳遞迴調對象
是由於回調的句柄被關聯到了elemData,也就是內部數據緩存中了
不可貴出jQuery的事件綁定機制:
jQuery對每個elem中的每一種事件,只會綁定一次事件處理函數(綁定這個elemData.handle),
而這個elemData.handle實際只作一件事,就是把event丟到jQuery內部的事件分發程序
jQuery.event.dispatch.apply( eventHandle.elem, arguments );
而不一樣的事件綁定,具體是由jQuery內部維護的事件列隊來區分(就是那個elemData.events)
在elemData中獲取到events和handle以後,接下來就須要知道此次綁定的是什麼事件了
畫了個簡單流程圖
本章只是事件的前段部分,解析完elemData數據,在執行期間又會如何處理,如何巧妙的運用這些設計,下章分曉!
若是以爲有幫助,請點擊下推薦,分享給更多有須要的人~