还不是完全清楚如何使用.on()进行jQuery事件绑定的同学先看这里http://api.jquery.com/on/
jQuery绑定事件的方法有几种,推荐使用.on()方法绑定,原因有两点:
1.on()方法可以绑定动态添加到页面元素的事件
比如动态添加到页面的DOM元素,用.on()方法绑定的事件不需要关心注册该事件的元素何时被添加进来,也不需要重复绑定。有的同学可能习惯于用.bind()、.live()或.delegate(),查看源码就会发现,它们实际上调用的都是.on()方法,并且.live()方法在jQuery1.9版本已经被移除。
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()方法。
2.on()方法绑定事件可以提升效率
很多文章都提到了利用事件冒泡和代理来提升事件绑定的效率,大多都没列出具体的差别,所以为了求证,我做一个小测试。
假设页面添加了5000个li,用chrome开发者工具Profiles测试页面载入时间。
普通绑定(姑且这么称呼它)
$('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()源码分析
.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; }