1+ /*
2+ Implementation Summary
3+
4+ 1. Enforce API surface and semantic compatibility with 1.9.x branch
5+ 2. Improve the module's maintainability by reducing the storage
6+ paths to a single mechanism.
7+ 3. Use the same single mechanism to support "private" and "user" data.
8+ 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
9+ 5. Avoid exposing implementation details on user objects (eg. expando properties)
10+ 6. Provide a clear path for implementation upgrade to WeakMap in 2014
11+ */
112var data_user , data_priv ,
213 rbrace = / (?: \{ [ \s \S ] * \} | \[ [ \s \S ] * \] ) $ / ,
314 rmultiDash = / ( [ A - Z ] ) / g;
415
516function Data ( ) {
6- // Nodes|Objects
7- this . owners = [ ] ;
8- // Data objects
9- this . cache = [ ] ;
17+ // Data objects. Keys correspond to the
18+ // unlocker that is accessible via "locker" method
19+ this . cache = { } ;
1020}
1121
12- Data . index = function ( array , node ) {
13- return array . indexOf ( node ) ;
14- } ;
15-
22+ Data . uid = 1 ;
1623
1724Data . prototype = {
18- add : function ( owner ) {
19- return ( this . cache [ this . owners . push ( owner ) - 1 ] = { } ) ;
25+ locker : function ( owner ) {
26+ var ovalueOf ,
27+ // Check if the owner object has already been outfitted with a valueOf
28+ // "locker". They "key" is the "Data" constructor itself, which is scoped
29+ // to the IIFE that wraps jQuery. This prevents outside tampering with the
30+ // "valueOf" locker.
31+ unlock = owner . valueOf ( Data ) ;
32+
33+ // If no "unlock" string exists, then create a valueOf "locker"
34+ // for storing the unlocker key. Since valueOf normally does not accept any
35+ // arguments, extant calls to valueOf will still behave as expected.
36+ if ( typeof unlock !== "string" ) {
37+ unlock = jQuery . expando + Data . uid ++ ;
38+ ovalueOf = owner . valueOf ;
39+
40+ Object . defineProperty ( owner , "valueOf" , {
41+ value : function ( pick ) {
42+ if ( pick === Data ) {
43+ return unlock ;
44+ }
45+ return ovalueOf . apply ( owner ) ;
46+ }
47+ // By omitting explicit [ enumerable, writable, configurable ]
48+ // they will default to "false"
49+ } ) ;
50+ }
51+
52+ // If private or user data already create a valueOf locker
53+ // then we'll reuse the unlock key, but still need to create
54+ // a cache object for this instance (could be private or user)
55+ if ( ! this . cache [ unlock ] ) {
56+ this . cache [ unlock ] = { } ;
57+ }
58+
59+ return unlock ;
2060 } ,
2161 set : function ( owner , data , value ) {
22- var prop ,
23- index = Data . index ( this . owners , owner ) ;
24-
25- // If there is no entry for this "owner", create one inline
26- // and set the index as though an owner entry had always existed
27- if ( index === - 1 ) {
28- this . add ( owner ) ;
29- index = this . owners . length - 1 ;
30- }
62+ var prop , cache , unlock ;
63+
64+ // There may be an unlock assigned to this node,
65+ // if there is no entry for this "owner", create one inline
66+ // and set the unlock as though an owner entry had always existed
67+ unlock = this . locker ( owner ) ;
68+ cache = this . cache [ unlock ] ;
69+
3170 // Handle: [ owner, key, value ] args
3271 if ( typeof data === "string" ) {
33- this . cache [ index ] [ data ] = value ;
72+ cache [ data ] = value ;
3473
3574 // Handle: [ owner, { properties } ] args
3675 } else {
37- // In the case where there was actually no "owner" entry and
38- // this.add ( owner ) was called to create one, there will be
76+ // [*] In the case where there was actually no "owner" entry and
77+ // this.locker ( owner ) was called to create one, there will be
3978 // a corresponding empty plain object in the cache.
40- if ( jQuery . isEmptyObject ( this . cache [ index ] ) ) {
41- this . cache [ index ] = data ;
42-
79+ //
80+ // Note, this will kill the reference between
81+ // "this.cache[ unlock ]" and "cache"
82+ if ( jQuery . isEmptyObject ( cache ) ) {
83+ cache = data ;
4384 // Otherwise, copy the properties one-by-one to the cache object
4485 } else {
4586 for ( prop in data ) {
46- this . cache [ index ] [ prop ] = data [ prop ] ;
87+ cache [ prop ] = data [ prop ] ;
4788 }
4889 }
4990 }
91+
92+ // [*] This is required to support an expectation made possible by the old
93+ // data system where plain objects used to initialize would be
94+ // set to the cache by reference, instead of having properties and
95+ // values copied.
96+ this . cache [ unlock ] = cache ;
97+
5098 return this ;
5199 } ,
52100 get : function ( owner , key ) {
53- var cache ,
54- index = Data . index ( this . owners , owner ) ;
55-
56- // A valid cache is found, or needs to be created.
57- // New entries will be added and return the current
58- // empty data object to be used as a return reference
59- // return this.add( owner );
60- // This logic was required by expectations made of the
61- // old data system.
62- cache = index === - 1 ?
63- this . add ( owner ) : this . cache [ index ] ;
101+ // Either a valid cache is found, or will be created.
102+ // New caches will be created and the unlock returned,
103+ // allowing direct access to the newly created
104+ // empty data object.
105+ var cache = this . cache [ this . locker ( owner ) ] ;
64106
65107 return key === undefined ?
66108 cache : cache [ key ] ;
@@ -87,9 +129,8 @@ Data.prototype = {
87129 } ,
88130 remove : function ( owner , key ) {
89131 var i , l , name ,
90- camel = jQuery . camelCase ,
91- index = Data . index ( this . owners , owner ) ,
92- cache = this . cache [ index ] ;
132+ unlock = this . locker ( owner ) ,
133+ cache = this . cache [ unlock ] ;
93134
94135 if ( key === undefined ) {
95136 cache = { } ;
@@ -98,14 +139,12 @@ Data.prototype = {
98139 // Support array or space separated string of keys
99140 if ( ! Array . isArray ( key ) ) {
100141 // Try the string as a key before any manipulation
101- //
102-
103142 if ( key in cache ) {
104143 name = [ key ] ;
105144 } else {
106145 // If a key with the spaces exists, use it.
107146 // Otherwise, create an array by matching non-whitespace
108- name = camel ( key ) ;
147+ name = jQuery . camelCase ( key ) ;
109148 name = name in cache ?
110149 [ name ] : ( name . match ( core_rnotwhite ) || [ ] ) ;
111150 }
@@ -116,7 +155,7 @@ Data.prototype = {
116155 // Since there is no way to tell _how_ a key was added, remove
117156 // both plain key and camelCase key. #12786
118157 // This will only penalize the array argument path.
119- name = key . concat ( key . map ( camel ) ) ;
158+ name = key . concat ( key . map ( jQuery . camelCase ) ) ;
120159 }
121160 i = 0 ;
122161 l = name . length ;
@@ -126,21 +165,15 @@ Data.prototype = {
126165 }
127166 }
128167 }
129- this . cache [ index ] = cache ;
168+ this . cache [ unlock ] = cache ;
130169 } ,
131170 hasData : function ( owner ) {
132- var index = Data . index ( this . owners , owner ) ;
133-
134- return index !== - 1 && ! jQuery . isEmptyObject ( this . cache [ index ] ) ;
171+ return ! jQuery . isEmptyObject (
172+ this . cache [ this . locker ( owner ) ]
173+ ) ;
135174 } ,
136175 discard : function ( owner ) {
137- var index = Data . index ( this . owners , owner ) ;
138-
139- if ( index !== - 1 ) {
140- this . owners . splice ( index , 1 ) ;
141- this . cache . splice ( index , 1 ) ;
142- }
143- return this ;
176+ delete this . cache [ this . locker ( owner ) ] ;
144177 }
145178} ;
146179
@@ -159,14 +192,15 @@ data_priv = new Data();
159192
160193
161194jQuery . extend ( {
195+ // Unique for each copy of jQuery on the page
196+ // Non-digits removed to match rinlinejQuery
197+ expando : "jQuery" + ( core_version + Math . random ( ) ) . replace ( / \D / g, "" ) ,
198+
162199 // This is no longer relevant to jQuery core, but must remain
163200 // supported for the sake of jQuery 1.9.x API surface compatibility.
164201 acceptData : function ( ) {
165202 return true ;
166203 } ,
167- // Unique for each copy of jQuery on the page
168- // Non-digits removed to match rinlinejQuery
169- expando : "jQuery" + ( core_version + Math . random ( ) ) . replace ( / \D / g, "" ) ,
170204
171205 hasData : function ( elem ) {
172206 return data_user . hasData ( elem ) || data_priv . hasData ( elem ) ;
@@ -245,7 +279,6 @@ jQuery.fn.extend({
245279 if ( data !== undefined ) {
246280 return data ;
247281 }
248-
249282 // Attempt to "discover" the data in
250283 // HTML5 custom data-* attrs
251284 data = dataAttr ( elem , key , undefined ) ;
@@ -320,6 +353,5 @@ function dataAttr( elem, key, data ) {
320353 data = undefined ;
321354 }
322355 }
323-
324356 return data ;
325357}
0 commit comments