mirror of
https://github.com/jquery/jquery.git
synced 2024-11-23 02:54:22 +00:00
Optimized Data rewrite
This commit is contained in:
parent
6a0ee2d9ed
commit
1d5d959ee0
150
src/data.js
150
src/data.js
@ -1,66 +1,108 @@
|
|||||||
|
/*
|
||||||
|
Implementation Summary
|
||||||
|
|
||||||
|
1. Enforce API surface and semantic compatibility with 1.9.x branch
|
||||||
|
2. Improve the module's maintainability by reducing the storage
|
||||||
|
paths to a single mechanism.
|
||||||
|
3. Use the same single mechanism to support "private" and "user" data.
|
||||||
|
4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
|
||||||
|
5. Avoid exposing implementation details on user objects (eg. expando properties)
|
||||||
|
6. Provide a clear path for implementation upgrade to WeakMap in 2014
|
||||||
|
*/
|
||||||
var data_user, data_priv,
|
var data_user, data_priv,
|
||||||
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
|
rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
|
||||||
rmultiDash = /([A-Z])/g;
|
rmultiDash = /([A-Z])/g;
|
||||||
|
|
||||||
function Data() {
|
function Data() {
|
||||||
// Nodes|Objects
|
// Data objects. Keys correspond to the
|
||||||
this.owners = [];
|
// unlocker that is accessible via "locker" method
|
||||||
// Data objects
|
this.cache = {};
|
||||||
this.cache = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Data.index = function( array, node ) {
|
Data.uid = 1;
|
||||||
return array.indexOf( node );
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Data.prototype = {
|
Data.prototype = {
|
||||||
add: function( owner ) {
|
locker: function( owner ) {
|
||||||
return (this.cache[ this.owners.push( owner ) - 1 ] = {});
|
var ovalueOf,
|
||||||
|
// Check if the owner object has already been outfitted with a valueOf
|
||||||
|
// "locker". They "key" is the "Data" constructor itself, which is scoped
|
||||||
|
// to the IIFE that wraps jQuery. This prevents outside tampering with the
|
||||||
|
// "valueOf" locker.
|
||||||
|
unlock = owner.valueOf( Data );
|
||||||
|
|
||||||
|
// If no "unlock" string exists, then create a valueOf "locker"
|
||||||
|
// for storing the unlocker key. Since valueOf normally does not accept any
|
||||||
|
// arguments, extant calls to valueOf will still behave as expected.
|
||||||
|
if ( typeof unlock !== "string" ) {
|
||||||
|
unlock = jQuery.expando + Data.uid++;
|
||||||
|
ovalueOf = owner.valueOf;
|
||||||
|
|
||||||
|
Object.defineProperty( owner, "valueOf", {
|
||||||
|
value: function( pick ) {
|
||||||
|
if ( pick === Data ) {
|
||||||
|
return unlock;
|
||||||
|
}
|
||||||
|
return ovalueOf.apply( owner );
|
||||||
|
}
|
||||||
|
// By omitting explicit [ enumerable, writable, configurable ]
|
||||||
|
// they will default to "false"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If private or user data already create a valueOf locker
|
||||||
|
// then we'll reuse the unlock key, but still need to create
|
||||||
|
// a cache object for this instance (could be private or user)
|
||||||
|
if ( !this.cache[ unlock ] ) {
|
||||||
|
this.cache[ unlock ] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlock;
|
||||||
},
|
},
|
||||||
set: function( owner, data, value ) {
|
set: function( owner, data, value ) {
|
||||||
var prop,
|
var prop, cache, unlock;
|
||||||
index = Data.index( this.owners, owner );
|
|
||||||
|
// There may be an unlock assigned to this node,
|
||||||
|
// if there is no entry for this "owner", create one inline
|
||||||
|
// and set the unlock as though an owner entry had always existed
|
||||||
|
unlock = this.locker( owner );
|
||||||
|
cache = this.cache[ unlock ];
|
||||||
|
|
||||||
// If there is no entry for this "owner", create one inline
|
|
||||||
// and set the index as though an owner entry had always existed
|
|
||||||
if ( index === -1 ) {
|
|
||||||
this.add( owner );
|
|
||||||
index = this.owners.length - 1;
|
|
||||||
}
|
|
||||||
// Handle: [ owner, key, value ] args
|
// Handle: [ owner, key, value ] args
|
||||||
if ( typeof data === "string" ) {
|
if ( typeof data === "string" ) {
|
||||||
this.cache[ index ][ data ] = value;
|
cache[ data ] = value;
|
||||||
|
|
||||||
// Handle: [ owner, { properties } ] args
|
// Handle: [ owner, { properties } ] args
|
||||||
} else {
|
} else {
|
||||||
// In the case where there was actually no "owner" entry and
|
// [*] In the case where there was actually no "owner" entry and
|
||||||
// this.add( owner ) was called to create one, there will be
|
// this.locker( owner ) was called to create one, there will be
|
||||||
// a corresponding empty plain object in the cache.
|
// a corresponding empty plain object in the cache.
|
||||||
if ( jQuery.isEmptyObject( this.cache[ index ] ) ) {
|
//
|
||||||
this.cache[ index ] = data;
|
// Note, this will kill the reference between
|
||||||
|
// "this.cache[ unlock ]" and "cache"
|
||||||
|
if ( jQuery.isEmptyObject( cache ) ) {
|
||||||
|
cache = data;
|
||||||
// Otherwise, copy the properties one-by-one to the cache object
|
// Otherwise, copy the properties one-by-one to the cache object
|
||||||
} else {
|
} else {
|
||||||
for ( prop in data ) {
|
for ( prop in data ) {
|
||||||
this.cache[ index ][ prop ] = data[ prop ];
|
cache[ prop ] = data[ prop ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [*] This is required to support an expectation made possible by the old
|
||||||
|
// data system where plain objects used to initialize would be
|
||||||
|
// set to the cache by reference, instead of having properties and
|
||||||
|
// values copied.
|
||||||
|
this.cache[ unlock ] = cache;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
get: function( owner, key ) {
|
get: function( owner, key ) {
|
||||||
var cache,
|
// Either a valid cache is found, or will be created.
|
||||||
index = Data.index( this.owners, owner );
|
// New caches will be created and the unlock returned,
|
||||||
|
// allowing direct access to the newly created
|
||||||
// A valid cache is found, or needs to be created.
|
// empty data object.
|
||||||
// New entries will be added and return the current
|
var cache = this.cache[ this.locker( owner ) ];
|
||||||
// empty data object to be used as a return reference
|
|
||||||
// return this.add( owner );
|
|
||||||
// This logic was required by expectations made of the
|
|
||||||
// old data system.
|
|
||||||
cache = index === -1 ?
|
|
||||||
this.add( owner ) : this.cache[ index ];
|
|
||||||
|
|
||||||
return key === undefined ?
|
return key === undefined ?
|
||||||
cache : cache[ key ];
|
cache : cache[ key ];
|
||||||
@ -87,9 +129,8 @@ Data.prototype = {
|
|||||||
},
|
},
|
||||||
remove: function( owner, key ) {
|
remove: function( owner, key ) {
|
||||||
var i, l, name,
|
var i, l, name,
|
||||||
camel = jQuery.camelCase,
|
unlock = this.locker( owner ),
|
||||||
index = Data.index( this.owners, owner ),
|
cache = this.cache[ unlock ];
|
||||||
cache = this.cache[ index ];
|
|
||||||
|
|
||||||
if ( key === undefined ) {
|
if ( key === undefined ) {
|
||||||
cache = {};
|
cache = {};
|
||||||
@ -98,14 +139,12 @@ Data.prototype = {
|
|||||||
// Support array or space separated string of keys
|
// Support array or space separated string of keys
|
||||||
if ( !Array.isArray( key ) ) {
|
if ( !Array.isArray( key ) ) {
|
||||||
// Try the string as a key before any manipulation
|
// Try the string as a key before any manipulation
|
||||||
//
|
|
||||||
|
|
||||||
if ( key in cache ) {
|
if ( key in cache ) {
|
||||||
name = [ key ];
|
name = [ key ];
|
||||||
} else {
|
} else {
|
||||||
// If a key with the spaces exists, use it.
|
// If a key with the spaces exists, use it.
|
||||||
// Otherwise, create an array by matching non-whitespace
|
// Otherwise, create an array by matching non-whitespace
|
||||||
name = camel( key );
|
name = jQuery.camelCase( key );
|
||||||
name = name in cache ?
|
name = name in cache ?
|
||||||
[ name ] : ( name.match( core_rnotwhite ) || [] );
|
[ name ] : ( name.match( core_rnotwhite ) || [] );
|
||||||
}
|
}
|
||||||
@ -116,7 +155,7 @@ Data.prototype = {
|
|||||||
// Since there is no way to tell _how_ a key was added, remove
|
// Since there is no way to tell _how_ a key was added, remove
|
||||||
// both plain key and camelCase key. #12786
|
// both plain key and camelCase key. #12786
|
||||||
// This will only penalize the array argument path.
|
// This will only penalize the array argument path.
|
||||||
name = key.concat( key.map( camel ) );
|
name = key.concat( key.map( jQuery.camelCase ) );
|
||||||
}
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
l = name.length;
|
l = name.length;
|
||||||
@ -126,21 +165,15 @@ Data.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.cache[ index ] = cache;
|
this.cache[ unlock ] = cache;
|
||||||
},
|
},
|
||||||
hasData: function( owner ) {
|
hasData: function( owner ) {
|
||||||
var index = Data.index( this.owners, owner );
|
return !jQuery.isEmptyObject(
|
||||||
|
this.cache[ this.locker( owner ) ]
|
||||||
return index !== -1 && !jQuery.isEmptyObject( this.cache[ index ] );
|
);
|
||||||
},
|
},
|
||||||
discard: function( owner ) {
|
discard: function( owner ) {
|
||||||
var index = Data.index( this.owners, owner );
|
delete this.cache[ this.locker( owner ) ];
|
||||||
|
|
||||||
if ( index !== -1 ) {
|
|
||||||
this.owners.splice( index, 1 );
|
|
||||||
this.cache.splice( index, 1 );
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,14 +192,15 @@ data_priv = new Data();
|
|||||||
|
|
||||||
|
|
||||||
jQuery.extend({
|
jQuery.extend({
|
||||||
|
// Unique for each copy of jQuery on the page
|
||||||
|
// Non-digits removed to match rinlinejQuery
|
||||||
|
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
|
||||||
|
|
||||||
// This is no longer relevant to jQuery core, but must remain
|
// This is no longer relevant to jQuery core, but must remain
|
||||||
// supported for the sake of jQuery 1.9.x API surface compatibility.
|
// supported for the sake of jQuery 1.9.x API surface compatibility.
|
||||||
acceptData: function() {
|
acceptData: function() {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// Unique for each copy of jQuery on the page
|
|
||||||
// Non-digits removed to match rinlinejQuery
|
|
||||||
expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
|
|
||||||
|
|
||||||
hasData: function( elem ) {
|
hasData: function( elem ) {
|
||||||
return data_user.hasData( elem ) || data_priv.hasData( elem );
|
return data_user.hasData( elem ) || data_priv.hasData( elem );
|
||||||
@ -245,7 +279,6 @@ jQuery.fn.extend({
|
|||||||
if ( data !== undefined ) {
|
if ( data !== undefined ) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to "discover" the data in
|
// Attempt to "discover" the data in
|
||||||
// HTML5 custom data-* attrs
|
// HTML5 custom data-* attrs
|
||||||
data = dataAttr( elem, key, undefined );
|
data = dataAttr( elem, key, undefined );
|
||||||
@ -320,6 +353,5 @@ function dataAttr( elem, key, data ) {
|
|||||||
data = undefined;
|
data = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,12 @@ test("jQuery.data(document)", 25, function() {
|
|||||||
QUnit.expectJqData(document, "foo");
|
QUnit.expectJqData(document, "foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Since the new data system does not rely on expandos, limiting the type of
|
||||||
|
// nodes that can have data is no longer necessary. jQuery.acceptData is now irrelevant
|
||||||
|
// and should eventually be removed from the library.
|
||||||
|
|
||||||
test("Data is not being set on comment and text nodes", function() {
|
test("Data is not being set on comment and text nodes", function() {
|
||||||
expect(2);
|
expect(2);
|
||||||
|
|
||||||
@ -133,10 +139,7 @@ test("Data is not being set on comment and text nodes", function() {
|
|||||||
ok( !jQuery.hasData( jQuery("<span>text</span>").contents().data("foo", 0) ) );
|
ok( !jQuery.hasData( jQuery("<span>text</span>").contents().data("foo", 0) ) );
|
||||||
|
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
// Since the new data system does not rely on exandos, limiting the type of
|
|
||||||
// nodes that can have data is no longer necessary. jQuery.acceptData is now irrelevant
|
|
||||||
// and should be removed from the library.
|
|
||||||
|
|
||||||
test("jQuery.acceptData", function() {
|
test("jQuery.acceptData", function() {
|
||||||
expect(9);
|
expect(9);
|
||||||
|
Loading…
Reference in New Issue
Block a user