Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is jQuery's .data method behaving like this? (Possible bug?)

This code best demonstrates my confusion.

var nativeObj, jWrapped, jSelector;

//WIAT = "What I Am Thinking"
nativeObj = $( '#tableTab' ) [0];  //WIAT: unwrap the jQuery object created by the selector and get the native DOM object
jWrapped = $( nativeObj );  //WIAT: wrap up the native DOM object again... should be equal to $( '#tableTab' )
jSelector = $( '#tableTab' );   //WIAT: pass the jQuery object as reference to jSelector variable

// set the data with jQuery's .data method
$.data( jWrapped, 'key', { test: 12 } );    //WIAT: will be equivalant to using $( '#tableTab' ) and should attach the data to it
$.data( $( '#tableTab' ) [0], 'key', { test: 34 } );    //WIAT: using the native DOM obj, it shouldn't work with this, since it doesn't specify in the docs
$.data( $( '#tableTab' ) , 'key', { test: 56 } );   //WIAT: should rewrite the data in the element to { key: { test: 56} }

console.log( $.data ( jWrapped ) ); // {key:{test:12}}
console.log( $.data ( jWrapped[0] ) );  // {key:{test:34}}
console.log( $.data ( nativeObj ) );    // {key:{test:34}}
console.log( $.data ( $( nativeObj ), 'test' ) );  // undefined  
console.log( $.data ( $( '#tableTab' ) [0] ) );  // {key:{test:34}}
console.log( $.data ( $( '#tableTab' ) , 'test' ) ); // undefined

Whoa, wait, what's going on?

1.Why am I getting different results? I only used 1 selector and am referencing one element.

2.Why aren't the object reference jWrapped and the object from $( '#tableTab' ) producing the same result?

3.Furthermore jWrapped and jWrapped[0] are producing different results? The former being a jQuery wrapped object and the latter a native DOM object. Essentially they are referencing the same element with a different result!??

//Now let's see what's inside the objects
console.log( $( '#tableTab' ) [0]);  // [object HTMLDivElement]         
console.log( nativeObj );  // [object HTMLDivElement]
console.log( $( nativeObj ) );  // {0:({}), context:({}), length:1}
console.log( jWrapped );   // {0:({}), context:({}), length:1, jQuery182021025872972076787:{toJSON:(function () {}), data:{key:{test:12}}}}
console.log( $( '#tableTab' ) );    // {length:1, 0:({}), context:({}), selector:"#tableTab"}
console.log( jSelector );   // {length:1, 0:({}), context:({}), selector:"#tableTab"}

Good nativeObj == $( '#tableTab' ) [0] that's what I expected

Whoa, that was weird, why doesn't jWrapped == $( nativeObj ) ?

Good, jSelector = $( '#tableTab' ) that's also what I expected

Given this data, I would extrapolate that $.data must accept a native DOM element, however

$( '#tableTab' ).data( 'key' , { test: 78 } );
console.log($( '#tableTab' ).data('key')); // 78

Umm excuse me monsieur console... not cool man.

Ok I am royally confused and frustrated and I hate jQuery and I hate Javascript and I hate IE... Ok no, I just hate IE, but that's another story. Why is jQuery behaving so strange? Hanging out with IE too much I think...

My guess is that it has to do with the way $.data works in jQuery and that it does not actually attach data to elements, but rather stores the data in its own object and references the data based on parsing the data passed that is passed it. Did I find a bug?

Help.

Also I did take a look at How does jQuery .data() work? and while it did provide some good information, it still doesn't answer what's happening here, which is my real question. Though it does confirm my idea that no data is stored in the elements, but in a jQuery object.

like image 593
Klik Avatar asked Oct 22 '22 17:10

Klik


1 Answers

Looking at the source in chrome developer tools, I found this comment around line 1564, in version 1.9 (GitHub source here, line 17 within function internalData)

// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically

So, whats happening here, is that when you pass in nativeObj, it will store the data in $.cache, but otherwise it will store the value onto the jQuery object you pass in.

take a look at this fiddle: http://jsfiddle.net/tomprogramming/SNqwh/

I made a few changes to your original example. The first is that I pass in the objects you set up at the top. The second is that you query the objects for a piece of data called "test", but that won't ever be there - you are storing an object that happens to have a property called test in it - under the property of "key".

At the end of your log statements, I added my own, using the object-oriented nature of $.data instead. Each time, I get the same result back. My guess here is that it uses the underlying dom node of each jQuery object to access the global cache, which in your case, has the value of {test:34}.

I do agree that this is unexpected behavior, as it would appear to the novice user that you are selecting the same element, but I believe this is just an underscore of the difference between $.data and $(selector).data(). The latter always uses the underlying dom node (and thus always be 'correct'), whereas the former will use the jQuery object you pass in, if available.

EDIT: this fiddle again underscores the difference. I set the value using $(selector).data(), and then pull it out again with $.data. The 'cache' used for the original objects hasn't changed (being the objects themselves), but the global cache has, for the underlying DOM node.

Lesson here: Always use a DOM node, or $().data. That is the "most consistent"

like image 104
Thomas Jones Avatar answered Oct 27 '22 10:10

Thomas Jones