diff --git a/src/data.js b/src/data.js index 7d9bef3e1..1545968fe 100644 --- a/src/data.js +++ b/src/data.js @@ -1,5 +1,179 @@ var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt /* For internal use only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} jQuery.extend({ cache: {}, @@ -27,183 +201,21 @@ jQuery.extend({ return !!elem && !isEmptyDataObject( elem ); }, - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; + data: function( elem, name, data ) { + return internalData( elem, name, data, false ); }, - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, l, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } + removeData: function( elem, name ) { + return internalRemoveData( elem, name, false ); }, // For internal use only. _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); }, // A method for determining if a DOM node can handle the data expando diff --git a/src/effects.js b/src/effects.js index 9965eee78..cff0dc835 100644 --- a/src/effects.js +++ b/src/effects.js @@ -327,7 +327,7 @@ function defaultPrefilter( elem, props, opts ) { } anim.done(function() { var prop; - jQuery.removeData( elem, "fxshow", true ); + jQuery._removeData( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } diff --git a/src/event.js b/src/event.js index 91ec7d757..3fea1a1be 100644 --- a/src/event.js +++ b/src/event.js @@ -15,7 +15,6 @@ var rformElems = /^(?:textarea|input|select)$/i, jQuery.event = { add: function( elem, types, handler, data, selector ) { - var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, handlers, special; @@ -196,7 +195,7 @@ jQuery.event = { // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete - jQuery.removeData( elem, "events", true ); + jQuery._removeData( elem, "events" ); } }, diff --git a/src/queue.js b/src/queue.js index d3a1136a6..c5a0cbd7d 100644 --- a/src/queue.js +++ b/src/queue.js @@ -58,8 +58,8 @@ jQuery.extend({ var key = type + "queueHooks"; return jQuery._data( elem, key ) || jQuery._data( elem, key, { empty: jQuery.Callbacks("once memory").add(function() { - jQuery.removeData( elem, type + "queue", true ); - jQuery.removeData( elem, key, true ); + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); }) }); } diff --git a/test/unit/data.js b/test/unit/data.js index 01d042e4e..d1d947f70 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -19,7 +19,7 @@ function dataTests (elem) { } var oldCacheLength, dataObj, internalDataObj, expected, actual; - + equal( jQuery.data(elem, "foo"), undefined, "No data exists initially" ); strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" ); @@ -50,7 +50,7 @@ function dataTests (elem) { strictEqual( jQuery.data(elem, "foo"), "foo1", "Passing an object extends the data object instead of replacing it" ); equal( jQuery.data(elem, "boom"), "bloz", "Extending the data object works" ); - jQuery._data(elem, "foo", "foo2"); + jQuery._data(elem, "foo", "foo2", true); equal( jQuery._data(elem, "foo"), "foo2", "Setting internal data works" ); equal( jQuery.data(elem, "foo"), "foo1", "Setting internal data does not override user data" ); @@ -66,9 +66,9 @@ function dataTests (elem) { jQuery.removeData(elem); strictEqual( jQuery._data(elem), internalDataObj, "jQuery.removeData does not remove internal data if it exists" ); - jQuery.removeData(elem, undefined, true); + jQuery._removeData(elem); - strictEqual( jQuery.data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); + strictEqual( jQuery._data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees all data has been removed from object" ); jQuery._data(elem, "foo", "foo2"); @@ -79,7 +79,7 @@ function dataTests (elem) { // delete the last private data key so we can test removing public data // will destroy the cache - jQuery.removeData( elem, "foo", true ); + jQuery._removeData( elem, "foo" ); if (elem.nodeType) { oldCacheLength = getCacheLength(); @@ -109,9 +109,9 @@ function dataTests (elem) { equal( jQuery.data(elem, "foo"), "foo1", "(sanity check) Ensure data is set in user data object" ); equal( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); - strictEqual( jQuery.data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); + strictEqual( jQuery._data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); jQuery._data(elem, "foo", "foo2"); equal( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); @@ -121,11 +121,11 @@ function dataTests (elem) { if ( elem.nodeType ) { oldCacheLength = getCacheLength(); - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); equal( getCacheLength(), oldCacheLength - 1, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); } else { - jQuery.removeData(elem, "foo", true); + jQuery._removeData(elem, "foo"); if (jQuery.support.deleteExpando) { expected = false;