From eed69eccc54d010889b5d8495320538d7ceb4e51 Mon Sep 17 00:00:00 2001 From: jeresig Date: Thu, 28 Jan 2010 14:12:44 -0500 Subject: [PATCH] Make sure that jQuery.data( elem ) always returns a data object, fixes #5971. Improve the performance of .bind() and .unbind(), fixes #5972. --- src/data.js | 17 ++++---- src/event.js | 120 +++++++++++++++++++++++++++++++++++------------------ test/unit/data.js | 13 +++--- 3 files changed, 92 insertions(+), 58 deletions(-) diff --git a/src/data.js b/src/data.js index e3dc190..0049a9f 100644 --- a/src/data.js +++ b/src/data.js @@ -1,5 +1,4 @@ var expando = "jQuery" + now(), uuid = 0, windowData = {}; -var emptyObject = {}; jQuery.extend({ cache: {}, @@ -25,8 +24,7 @@ jQuery.extend({ var id = elem[ expando ], cache = jQuery.cache, thisCache; - // Handle the case where there's no name immediately - if ( !name && !id ) { + if ( !id && typeof name === "string" && data === undefined ) { return null; } @@ -40,17 +38,16 @@ jQuery.extend({ if ( typeof name === "object" ) { elem[ expando ] = id; thisCache = cache[ id ] = jQuery.extend(true, {}, name); - } else if ( cache[ id ] ) { - thisCache = cache[ id ]; - } else if ( typeof data === "undefined" ) { - thisCache = emptyObject; - } else { - thisCache = cache[ id ] = {}; + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; } + thisCache = cache[ id ]; + // Prevent overriding the named cache with undefined values if ( data !== undefined ) { - elem[ expando ] = id; thisCache[ name ] = data; } diff --git a/src/event.js b/src/event.js index efefe57..2978bc6 100644 --- a/src/event.js +++ b/src/event.js @@ -42,8 +42,16 @@ jQuery.event = { } // Init the element's event structure - var events = jQuery.data( elem, "events" ) || jQuery.data( elem, "events", {} ), - handle = jQuery.data( elem, "handle" ), eventHandle; + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events || (elemData.events = {}), + handle = elemData.handle, eventHandle; if ( !handle ) { eventHandle = function() { @@ -54,13 +62,7 @@ jQuery.event = { undefined; }; - handle = jQuery.data( elem, "handle", eventHandle ); - } - - // If no handle is found then we must be trying to bind to one of the - // banned noData elements - if ( !handle ) { - return; + handle = elemData.handle = eventHandle; } // Add elem as a property of the handle function @@ -70,15 +72,11 @@ jQuery.event = { // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); - types = types.split( /\s+/ ); + types = types.split(" "); - var type, i = 0; + var type, i = 0, namespaces; while ( (type = types[ i++ ]) ) { - // Namespaced event handlers - var namespaces = type.split("."); - type = namespaces.shift(); - if ( i > 1 ) { handler = jQuery.proxy( handler ); @@ -87,7 +85,16 @@ jQuery.event = { } } - handler.type = namespaces.slice(0).sort().join("."); + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handler.type = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handler.type = ""; + } // Get the current list of functions bound to this event var handlers = events[ type ], @@ -104,6 +111,7 @@ jQuery.event = { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, handle, false ); + } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, handle ); } @@ -140,7 +148,13 @@ jQuery.event = { return; } - var events = jQuery.data( elem, "events" ), ret, type, fn; + var elemData = jQuery.data( elem ); + + if ( !elemData ) { + return; + } + + var events = elemData.events, ret, type, fn; if ( events ) { // Unbind all events for the element @@ -148,6 +162,7 @@ jQuery.event = { for ( type in events ) { this.remove( elem, type + (types || "") ); } + } else { // types is actually an event object here if ( types.type ) { @@ -157,16 +172,24 @@ jQuery.event = { // Handle multiple events separated by a space // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(/\s+/); - var i = 0; + types = types.split(" "); + + var i = 0, all, namespaces, namespace; + while ( (type = types[ i++ ]) ) { - // Namespaced event handlers - var namespaces = type.split("."); - type = namespaces.shift(); - var all = !namespaces.length, - cleaned = jQuery.map( namespaces.slice(0).sort(), fcleanup ), - namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"), - special = this.special[ type ] || {}; + all = type.indexOf(".") < 0; + namespaces = null; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + var special = this.special[ type ] || {}; if ( events[ type ] ) { // remove the given handler for the given type @@ -185,21 +208,23 @@ jQuery.event = { } if ( special.remove ) { - special.remove.call( elem, namespaces, fn); + special.remove.call( elem, namespaces || [], fn); } // remove generic event handler if no more handlers exist for ( ret in events[ type ] ) { + break; } if ( !ret ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { if ( elem.removeEventListener ) { - elem.removeEventListener( type, jQuery.data( elem, "handle" ), false ); + elem.removeEventListener( type, elemData.handle, false ); } else if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, jQuery.data( elem, "handle" ) ); + elem.detachEvent( "on" + type, elemData.handle ); } } + ret = null; delete events[ type ]; } @@ -211,13 +236,19 @@ jQuery.event = { for ( ret in events ) { break; } + if ( !ret ) { - var handle = jQuery.data( elem, "handle" ); + var handle = elemData.handle; if ( handle ) { handle.elem = null; } - jQuery.removeData( elem, "events" ); - jQuery.removeData( elem, "handle" ); + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } } } }, @@ -796,11 +827,16 @@ jQuery.each(["bind", "one"], function( i, name ) { return fn.apply( this, arguments ); }) : fn; - return type === "unload" && name !== "one" ? - this.one( type, data, fn ) : - this.each(function() { - jQuery.event.add( this, type, handler, data ); - }); + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; }; }); @@ -811,12 +847,14 @@ jQuery.fn.extend({ for ( var key in type ) { this.unbind(key, type[key]); } - return this; + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } } - return this.each(function() { - jQuery.event.remove( this, type, fn ); - }); + return this; }, trigger: function( type, data ) { return this.each(function() { diff --git a/test/unit/data.js b/test/unit/data.js index fd03c54..79b23c6 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -1,20 +1,19 @@ module("data"); test("expando", function(){ - expect(7); + expect(6); equals("expando" in jQuery, true, "jQuery is exposing the expando"); var obj = {}; jQuery.data(obj); - equals( jQuery.expando in obj, false, "jQuery.data did not add an expando to the object" ); - - jQuery.data(obj, true); - equals( jQuery.expando in obj, false, "jQuery.data did not add an expando to the object" ); - + equals( jQuery.expando in obj, true, "jQuery.data adds an expando to the object" ); + + obj = {}; jQuery.data(obj, 'test'); equals( jQuery.expando in obj, false, "jQuery.data did not add an expando to the object" ); - + + obj = {}; jQuery.data(obj, "foo", "bar"); equals( jQuery.expando in obj, true, "jQuery.data added an expando to the object" ); -- 1.7.10.4