還不是徹底清楚如何使用.on()進行jQuery事件綁定的同窗先看這裏http://api.jquery.com/on/html
jQuery綁定事件的方法有幾種,推薦使用.on()方法綁定,緣由有兩點:node
好比動態添加到頁面的DOM元素,用.on()方法綁定的事件不須要關心註冊該事件的元素什麼時候被添加進來,也不須要重複綁定。有的同窗可能習慣於用.bind()、.live()或.delegate(),查看源碼就會發現,它們實際上調用的都是.on()方法,而且.live()方法在jQuery1.9版本已經被移除。jquery
bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }
移除.on()綁定的事件用.off()方法。chrome
不少文章都提到了利用事件冒泡和代理來提高事件綁定的效率,大多都沒列出具體的差異,因此爲了求證,我作一個小測試。api
假設頁面添加了5000個li,用chrome開發者工具Profiles測試頁面載入時間。瀏覽器
普通綁定(姑且這麼稱呼它)app
$('li').click(function(){ console.log(this) });
綁定過程的執行時間函數
普通綁定至關於在5000li上面分別註冊click事件,內存佔用約4.2M,綁定時間約爲72ms。工具
.on()綁定源碼分析
$(document).on('click', 'li', function(){ console.log(this) })
綁定過程的執行時間
.on()綁定利用事件代理,只在document上註冊了一個click事件,內存佔用約2.2M,綁定時間約爲1ms。
.on()方法分析包含其調用的兩個主要方法:
.add()進行事件註冊
.dispatch()進行事件代理
/* jQuery 1.10.2 */ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var type, origFn; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } // 遍歷types對象,針對每個屬性綁定on()方法 // 將types[type]做爲fn傳入 for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } // 參數修正 // jQuery這種參數修正的方法很好 // 能夠兼容多種參數形式 // 可見在靈活調用的背後作了不少處理 if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { // fn傳入false時,阻止該事件的默認行爲 // function returnFalse() {return false;} fn = returnFalse; } else if ( !fn ) { return this; } // one()調用on() if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info // 用一個空jQuery對象,這樣可使用.off方法, // 而且event帶有remove事件須要的信息 jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn // 事件刪除依賴於guid fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } // 這裏調用jQuery的each方法遍歷調用on()方法的jQuery對象 // 如$('li').on(...)則遍歷每個li傳入add() // 推薦使用$(document).on()或者集合元素的父元素 return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, // 事件註冊 add: function( elem, types, handler, data, selector ) { var tmp, events, t, handleObjIn, special, eventHandle, handleObj, handlers, type, namespaces, origType, elemData = jQuery._data( elem ); // Don't attach events to noData or // text/comment nodes (but allow plain objects) // 不符合綁定條件的節點 if ( !elemData ) { return; } // Caller can pass in an object of custom data in lieu of the handler // 傳入的handler爲事件對象 if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, // used to find/remove it later // 爲handler分配一個ID,用於以後的查找或刪除 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, // if this is the first // 初始化events結構 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; }; // Add elem as a property of the handle fn // to prevent a memory leak with IE non-native events // 添加elem爲eventHandle的屬性,防止IE非本地事件的內存泄露? // 搜索整個源碼,只有110行用到了eventHandle.elem eventHandle.elem = elem; } // Handle multiple events separated by a space // 處理多個以空格分隔的事件類型 types = ( types || "" ).match( core_rnotwhite ) || [""]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; type = origType = tmp[1]; // 存儲全部命名空間 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 type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers 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 // 初始化handler隊列,只初始化一次 if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent // if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element // 二級DOM事件/IE事件模型 // eventHandle會調用jQuery.event.dispatch進行事件代理 if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); 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 // 將變量置空,防止循環引用致使IE內存泄露 elem = null; }, // 事件代理 dispatch: function( event ) { // Make a writable jQuery.Event from the native event object // jQuery定義的event對象,兼容標準事件模型與IE事件模型 event = jQuery.event.fix( event ); var i, ret, handleObj, matched, j, handlerQueue = [], args = core_slice.call( arguments ), handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event // 使用jQuery.Event代替瀏覽器的event args[0] = event; // 事件的代理節點,好比document event.delegateTarget = this; // Call the preDispatch hook for the mapped type, // and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers // 遍歷事件發生節點至代理節點之間的全部節點 // 匹配每個發生節點=?綁定節點 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; // 遍歷匹配的節點,而且沒有被阻止冒泡 while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { // Triggered event must either 1) have no namespace, or // 2) have namespace(s) a subset or equal to those // in the bound event (both can have no namespace). if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; // 傳入綁定事件的具體節點,調用事件發生函數 ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }