jQuery 1.9.1源碼分析系列(十)事件系統(tǒng)之綁定事件
事件綁定的方法有很多種,使用了jquery那么原理那種綁定方式(elem.click = function(){...}))就不太想推薦給大家了。最主要的原因是elem.click=fn這種方式只能綁定一個事件處理,多次綁定的只會保留最后一次綁定的結(jié)果。
下面給大家介紹jquery綁定事件的方式有哪些吧。
jQuery.fn.eventType([[data,] fn])
比如eventType指的是事件類型,比如click: $("#chua").click(fn);
data這個參數(shù)一般都不會使用。這種方式事件綁定在("#chua")上,沒有委托事件,和js原生的事件綁定更接近。我們看一下源碼
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { //合并15種事件統(tǒng)一增加到j(luò)Query.fn上,內(nèi)部調(diào)用this.on / this.trigger jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? this.on( name, null, data, fn ) : //如果不帶參數(shù)表示立刻觸發(fā)指定事件 this.trigger( name ); }; }); jQuery.fn.bind( types[, data], fn )
比如$("#chua").bind("click",fn)。直接將事件綁定到$("#chua")上,沒有委托事件。源碼
bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); } jQuery.fn.delegate(selector, types[, data], fn)
顧名思義delegate這個函數(shù)是用來做事件委托的,將選擇器selector對應(yīng)的響應(yīng)處理委托給當前jQuery所匹配的元素。
比如:$(document).delegate('#big',"click",dohander);分析到這里順便分析一下事件委托的處理流程。
當點擊"#big"元素的時候,事件click會冒泡直到document節(jié)點;
document綁定了處理事件,這個處理事件會調(diào)用到事件分發(fā)器dispatch;
dispatch中取出對應(yīng)事件類型click的所有的委托事件列表handlers;
根據(jù)事件源event.target過濾出委托事件列表handlers中每一個元素的selector屬性對應(yīng)的節(jié)點處于事件原和委托節(jié)點document之間(包括事件源)的委托事件,保存為handlerQueue;
執(zhí)行handlerQueue里面的事件處理。
上面是一個大致的流程,后續(xù)會詳細分析。先看delegate源碼
delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); } jQuery.fn.one( types[, selector[, data]], fn )
通過one()函數(shù)綁定的事件處理函數(shù)都是一次性的,只有首次觸發(fā)事件時會執(zhí)行該事件處理函數(shù)。觸發(fā)之后,jQuery就會移除當前事件綁定。
比如$("#chua").one("click",fn);為#chua節(jié)點綁定一次性的click事件
$(document).one("click","#chua",fn);將#chua的click事件委托給document處理。源碼
one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); } jQuery.fn.trigger(type[, data]) jQuery.fn.triggerHandler(type[, data])
trigger觸發(fā)jQuery對象所匹配的每一個元素對應(yīng)type類型的事件。比如$("#chua").trigger("click");
triggeHandler只觸發(fā)jQuery對象所匹配的元素中的第一個元素對應(yīng)的type類型的事件,且不會觸發(fā)事件的默認行為。
//立刻觸發(fā)jQuery對象內(nèi)所有元素的指定type的事件 trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, //立刻觸發(fā)jQuery對象內(nèi)第一個元素的指定type的事件,且不會觸發(fā)事件(比如表單提交)的默認行為 triggerHandler: function( type, data ) { var elem = this[0]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } }
上面分析了那么些個事件綁定,有么有發(fā)現(xiàn)他們都是使用.on方式綁定的?這也是為什么提倡統(tǒng)一使用on來綁定的原因(one方式除外)。
jQuery.fn.on( types[, selector[, data]], fn )
.on的事件綁定一半的代碼都實在處理傳遞不同參數(shù)的處理,這也是jQuery的口號Write less, do more的代價吧。最終使用jQuery.event.add來綁定事件。
jQuery.event.add綁定事件有幾個比較關(guān)鍵的地方:
第一個,使用內(nèi)部緩存來保存節(jié)點elem的事件信息
//獲取緩存數(shù)據(jù) elemData = jQuery._data( elem ); ... //設(shè)置緩存數(shù)據(jù) if ( !(events = elemData.events) ) { events = elemData.events = {}; } if ( !(eventHandle = elemData.handle) ) { eventHandle = elemData.handle = function( e ) { ... }; //將elem作為handle函數(shù)的一個特征防止ie非本地事件引起的內(nèi)存泄露 eventHandle.elem = elem; }
第二個,設(shè)置綁定事件信息,特別是指定的選擇器selector、響應(yīng)處理handler、響應(yīng)事件類型type、命名空間namespace
// handleObj:設(shè)置綁定事件信息。貫穿整個事件處理 handleObj = jQuery.extend({ type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, // For use in libraries implementing .is(). We use this for POS matching in `select` //"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + //whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) //用來判斷親密關(guān)系 needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join(".") }, handleObjIn );
第三個,節(jié)點的事件列表中,真正的委托事件列表放置在前面,和delegateCount屬性同步,即events.click.length假設(shè)為3,events.click.delegateCount假設(shè)為2。那么events.click[0]和events.click[1]所指定事件是委托事件。第三個events.click[2]對應(yīng)的事件不是委托事件,而是節(jié)點自身的事件。
//將事件對象handleObj添加到元素的處理列表,委托事件放在前面,委托代理計數(shù)遞增 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); }
源碼和添加事件后的結(jié)構(gòu)上一章已經(jīng)分析,詳情請點擊查看
綁定有一個公用函數(shù)jQuery.fn.on。解綁同樣有一個公用函數(shù)jQuery.fn.off
jQuery.fn.off([ types[, selector][, fn]] )
這里的傳參有個比較特殊的情況:當types是瀏覽器事件對象event的時候,表示要去掉(解綁)委托節(jié)點上event.selector指定的委托事件
//傳入的參數(shù)是事件且綁定了處理函數(shù) if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; //types.delegateTarget是事件托管對象 jQuery( types.delegateTarget ).off( //組合jQuery識別的type handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; }
無論如何最終都是調(diào)用jQuery.event.remove函數(shù)來解綁事件。
jQuery.fn.off完整的源碼如下
off: function( types, selector, fn ) { var handleObj, type; //傳入的參數(shù)是事件且綁定了處理函數(shù) if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; //types.delegateTarget是事件托管對象 jQuery( types.delegateTarget ).off( //組合jQuery識別的type handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }
接下來分析一下事件解綁的低級api jQuery.event.remove。
jQuery.event.remove
jQuery使用.off()函數(shù)傷處綁定的事件時內(nèi)部調(diào)用的基礎(chǔ)函數(shù)是jQuery.event.remove。該函數(shù)的處理流程如下
1. 分解傳入的要刪除的事件類型types,遍歷類型,如果要刪除的事件沒有事件名,只有命名空間則表示刪除該命名空間下所有綁定事件
//分解types為type.namespace為單位元素的數(shù)組 types = ( types || "" ).match( core_rnotwhite ) || [""]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; namespaces = ( tmp[2] || "" ).split( "." ).sort(); //解綁當前元素的當前命名空間(types[ t ])上所有的事件 if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } ...
2. 遍歷類型過程中,刪除匹配的事件,代理計數(shù)修正
type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); //刪除匹配事件 origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; //各種滿足移除事件的條件才能移除 if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } }
3. 如果節(jié)點上指定類型的事件處理器已經(jīng)為空,則將events上的該類型的事件處理對象移除
// 移除事件處理對象 // (移除特殊事件處理過程中避免潛在的無限遞歸,下一章會專門詳解這種情況) if ( origCount && !handlers.length ) { //例如 var js_obj = document.createElement("div"); js_obj.onclick = function(){ …} //上面的js_obj是一個DOM元素的引用,DOM元素它長期在網(wǎng)頁當中,不會消失,而這個DOM元素的一屬性onclick,又是內(nèi)部的函數(shù)引用(閉包),而這個匿名函數(shù)又和js_obj之間有隱藏的關(guān)聯(lián)(作用域鏈)所以形成了一個,循環(huán)引用. if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; }
4. 如果節(jié)點上沒有任何綁定的事件,則清空事件處理入口handle
if ( jQuery.isEmptyObject( events ) ) { delete elemData.handle; //removeData還檢事件對象是否為空,所以使用它替代delete jQuery._removeData( elem, "events" ); }
拓展: 瀏覽器事件刪除jQuery.removeEvent
jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { var name = "on" + type; if ( elem.detachEvent ) { // #8545, #7054,避免自定義事件在IE6-8中的內(nèi)存泄露 // detachEvent需要傳遞第一個參數(shù),不能是undefined的 if ( typeof elem[ name ] === core_strundefined ) { elem[ name ] = null; } elem.detachEvent( name, handle ); } };
以上內(nèi)容是小編給大家介紹的jQuery 1.9.1源碼分析系列(十)事件系統(tǒng)之綁定事件,希望大家喜歡。
- jQuery綁定事件監(jiān)聽bind和移除事件監(jiān)聽unbind用法實例詳解
- jQuery中bind(),live(),delegate(),on()綁定事件方法實例詳解
- 淺談Jquery為元素綁定事件
- jquery利用命名空間移除綁定事件的方法
- JQuery選擇器綁定事件及修改內(nèi)容的方法
- jQuery級聯(lián)操作綁定事件實例
- jQuery動態(tài)添加的元素綁定事件處理函數(shù)代碼
- jQuery中的bind綁定事件與文本框改變事件的臨時解決方法
- jQuery給動態(tài)添加的元素綁定事件的方法
- JavaScript中利用jQuery綁定事件的幾種方式小結(jié)
相關(guān)文章
基于jQuery實現(xiàn)的旋轉(zhuǎn)彩圈實例
這篇文章主要介紹了基于jQuery實現(xiàn)的旋轉(zhuǎn)彩圈,涉及jQuery定時操作頁面元素的相關(guān)技巧,需要的朋友可以參考下2015-06-06jQuery用FormData實現(xiàn)文件上傳的方法
眾所周知文件上傳是Web開發(fā)中的重要話題,最直接和簡單的方式是通過表單直接提交文件。 下面這篇文章小編就來和大家分享jQuery利用FormData實現(xiàn)文件上傳的方法,文中介紹的方法簡單易懂,相信對大家的理解和學(xué)習(xí)很有幫助,有需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2016-11-11關(guān)于Jquery操作Cookie取值錯誤的解決方法
使用JQuery操作cookie時 發(fā)生取的值不正確,結(jié)果發(fā)現(xiàn)cookie有四個不同的屬性,下面與大家分享下錯誤的原因及解決方法2013-08-08Tab頁界面,用jQuery及Ajax技術(shù)實現(xiàn)
從桌面開發(fā)的時代開始,Tab頁就是一個優(yōu)異的界面布局形式,兼有菜單的樣式和充分復(fù)用有限的界面的優(yōu)點。2009-09-09