]> source.dussan.org Git - jquery.git/commitdiff
Data: move element cache to element[expando]
authorRick Waldron <waldron.rick@gmail.com>
Mon, 11 Nov 2013 18:13:22 +0000 (13:13 -0500)
committerTimmy Willison <timmywillisn@gmail.com>
Wed, 4 Mar 2015 22:26:47 +0000 (17:26 -0500)
- avoid explicit data.discard() cleanup calls
- explicitly remove the data.events property, only when private data exists
- reduces code footprint

Fixes gh-1734
Close gh-1428

src/data/Data.js
src/event.js
src/manipulation.js
test/unit/manipulation.js

index 7b9ec5b54ee9e9cbf1dec4e6f9c9e5b3caf9914e..ec108e06217d3111703ce8187d91a43546d5289a 100644 (file)
@@ -5,15 +5,6 @@ define([
 ], function( jQuery, rnotwhite ) {
 
 function Data() {
-       // Support: Android<4,
-       // Old WebKit does not have Object.preventExtensions/freeze method,
-       // return new empty object instead with no [[set]] accessor
-       Object.defineProperty( this.cache = {}, 0, {
-               get: function() {
-                       return {};
-               }
-       });
-
        this.expando = jQuery.expando + Data.uid++;
 }
 
@@ -21,45 +12,60 @@ Data.uid = 1;
 Data.accepts = jQuery.acceptData;
 
 Data.prototype = {
-       key: function( owner ) {
-               // We can accept data for non-element nodes in modern browsers,
-               // but we should not, see #8335.
-               // Always return the key for a frozen object.
-               if ( !Data.accepts( owner ) ) {
-                       return 0;
-               }
-
-               // Check if the owner object already has a cache key
-               var unlock = owner[ this.expando ];
 
-               // If not, create one
-               if ( !unlock ) {
-                       unlock = Data.uid++;
+       register: function( owner, initial ) {
+               var descriptor = {},
+                       value = initial || {};
 
+               try {
                        // If it is a node unlikely to be stringify-ed or looped over
                        // use plain assignment
                        if ( owner.nodeType ) {
-                               owner[ this.expando ] = unlock;
+                               owner[ this.expando ] = value;
+
                        // Otherwise secure it in a non-enumerable, non-writable property
+                       // configurability must be true to allow the property to be
+                       // deleted with the delete operator
                        } else {
-                               Object.defineProperty( owner, this.expando, { value: unlock } );
+                               descriptor[ this.expando ] = {
+                                       value: value,
+                                       writable: true,
+                                       configurable: true
+                               };
+                               Object.defineProperties( owner, descriptor );
                        }
+
+               // Support: Android < 4
+               // Fallback to a less secure definition
+               } catch ( e ) {
+                       descriptor[ this.expando ] = value;
+                       jQuery.extend( owner, descriptor );
                }
 
-               // Ensure the cache object
-               if ( !this.cache[ unlock ] ) {
-                       this.cache[ unlock ] = {};
+               return owner[ this.expando ];
+       },
+       cache: function( owner, initial ) {
+               // We can accept data for non-element nodes in modern browsers,
+               // but we should not, see #8335.
+               // Always return an empty object.
+               if ( !Data.accepts( owner ) ) {
+                       return {};
                }
 
-               return unlock;
+               // Check if the owner object already has a cache
+               var cache = owner[ this.expando ];
+
+               // If so, return it
+               if ( cache ) {
+                       return cache;
+               }
+
+               // If not, register one
+               return this.register( owner, initial );
        },
        set: function( owner, data, value ) {
                var prop,
-                       // 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.key( owner ),
-                       cache = this.cache[ unlock ];
+                       cache = this.cache( owner );
 
                // Handle: [ owner, key, value ] args
                if ( typeof data === "string" ) {
@@ -69,7 +75,8 @@ Data.prototype = {
                } else {
                        // Fresh assignments by object are shallow copied
                        if ( jQuery.isEmptyObject( cache ) ) {
-                               jQuery.extend( this.cache[ unlock ], data );
+
+                               jQuery.extend( cache, data );
                        // Otherwise, copy the properties one-by-one to the cache object
                        } else {
                                for ( prop in data ) {
@@ -80,11 +87,7 @@ Data.prototype = {
                return cache;
        },
        get: function( owner, key ) {
-               // Either a valid cache is found, or will be created.
-               // New caches will be created and the unlock returned,
-               // allowing direct access to the newly created
-               // empty data object. A valid owner object must be provided.
-               var cache = this.cache[ this.key( owner ) ];
+               var cache = this.cache( owner );
 
                return key === undefined ?
                        cache : cache[ key ];
@@ -125,11 +128,10 @@ Data.prototype = {
        },
        remove: function( owner, key ) {
                var i, name, camel,
-                       unlock = this.key( owner ),
-                       cache = this.cache[ unlock ];
+                       cache = this.cache( owner );
 
                if ( key === undefined ) {
-                       this.cache[ unlock ] = {};
+                       this.register( owner );
 
                } else {
                        // Support array or space separated string of keys
@@ -156,19 +158,19 @@ Data.prototype = {
                        }
 
                        i = name.length;
+
                        while ( i-- ) {
                                delete cache[ name[ i ] ];
                        }
                }
        },
        hasData: function( owner ) {
-               return !jQuery.isEmptyObject(
-                       this.cache[ owner[ this.expando ] ] || {}
-               );
+               var cache = owner[ this.expando ];
+               return cache !== undefined && !jQuery.isEmptyObject( cache );
        },
        discard: function( owner ) {
                if ( owner[ this.expando ] ) {
-                       delete this.cache[ owner[ this.expando ] ];
+                       delete owner[ this.expando ];
                }
        }
 };
index ae0539bb903cb0c8899efdf304eba5b219d47497..4849ef3f09cdd8cfe700c4b2b0e0c75c6db534ef 100644 (file)
@@ -216,8 +216,12 @@ jQuery.event = {
 
                // Remove the expando if it's no longer used
                if ( jQuery.isEmptyObject( events ) ) {
+                       // Normally this should go through the data api
+                       // but since event.js owns these properties,
+                       // this exception is made for the sake of optimizing
+                       // the operation.
                        delete elemData.handle;
-                       dataPriv.remove( elem, "events" );
+                       delete elemData.events;
                }
        },
 
index 7c9f5049a2db6f100046529636308a934fe3e694..be982dc2dcfb07cd363b8665d8920e6b05e5874d 100644 (file)
@@ -288,34 +288,25 @@ jQuery.extend({
        },
 
        cleanData: function( elems ) {
-               var data, elem, type, key,
+               var data, elem, type,
                        special = jQuery.event.special,
                        i = 0;
 
                for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
-                       if ( jQuery.acceptData( elem ) ) {
-                               key = elem[ dataPriv.expando ];
-
-                               if ( key && (data = dataPriv.cache[ key ]) ) {
-                                       if ( data.events ) {
-                                               for ( type in data.events ) {
-                                                       if ( special[ type ] ) {
-                                                               jQuery.event.remove( elem, type );
-
-                                                       // This is a shortcut to avoid jQuery.event.remove's overhead
-                                                       } else {
-                                                               jQuery.removeEvent( elem, type, data.handle );
-                                                       }
+                       if ( jQuery.acceptData( elem ) && (data = elem[ dataPriv.expando ])) {
+                               if ( data.events ) {
+                                       for ( type in data.events ) {
+                                               if ( special[ type ] ) {
+                                                       jQuery.event.remove( elem, type );
+
+                                               // This is a shortcut to avoid jQuery.event.remove's overhead
+                                               } else {
+                                                       jQuery.removeEvent( elem, type, data.handle );
                                                }
                                        }
-                                       if ( dataPriv.cache[ key ] ) {
-                                               // Discard any remaining `private` data
-                                               delete dataPriv.cache[ key ];
-                                       }
                                }
+                               delete data.events;
                        }
-                       // Discard any remaining `user` data
-                       delete dataUser.cache[ elem[ dataUser.expando ] ];
                }
        }
 });
index bd324a8e2b0d0a9728f3fe4ab2f5d25f80c0d738..46a9a8a52b78abb04b95642886456e73602d6fd1 100644 (file)
@@ -64,7 +64,7 @@ test( "text(undefined)", function() {
 
 function testText( valueObj ) {
 
-       expect( 7 );
+       expect( 6 );
 
        var val, j, expected, $multipleElements, $parentDiv, $childDiv;
 
@@ -94,8 +94,6 @@ function testText( valueObj ) {
        $parentDiv = jQuery( "<div/>" );
        $parentDiv.append( $childDiv );
        $parentDiv.text("Dry off");
-
-       equal( $childDiv.data("leak"), undefined, "Check for leaks (#11809)" );
 }
 
 test( "text(String)", function() {
@@ -1814,7 +1812,7 @@ test( "clone()/html() don't expose jQuery/Sizzle expandos (#12858)", function()
 
 test( "remove() no filters", function() {
 
-  expect( 3 );
+  expect( 2 );
 
        var first = jQuery("#ap").children().first();
 
@@ -1823,9 +1821,6 @@ test( "remove() no filters", function() {
        jQuery("#ap").children().remove();
        ok( jQuery("#ap").text().length > 10, "Check text is not removed" );
        equal( jQuery("#ap").children().length, 0, "Check remove" );
-
-       equal( first.data("foo"), null, "first data" );
-
 });
 
 test( "remove() with filters", function() {