How jQuery data() breaks circular reference - javascript

I have read an why it's better and how it's implemented. But what i don't really understand is how does it break the circular reference?.
how does it break the reference circle?
$(div1).data('item', div2);
$(div2).data('item', div1);
like example, the divs above point to each other, how is it prevented? I have a hunch, but i just want to make sure if my hunch is right.

The circular reference problem happens in some browsers when you put a reference to a DOM object on a DOM object as a property on that DOM object. Then, you have two DOM objects pointing at each other. Removing a DOM object with a custom property on it doesn't clear that custom property. A garbage collector that isn't that smart doesn't realize that this DOM reference doesn't count so it gets stuck and there are several ways that this can lead to leaks.
.data() solves this problem because the .data() data is NOT on the DOM object. It's just a javascript data structure that can be associated with the DOM object via a unique string ID.
The one confusing part of this is that when you read with .data("key") and the key isn't found in the javascript .data() data structure, then and only then, jQuery will look for an attribute on the DOM object called "data-key". But whenever you write with .data("key", "myData"), it never writes to the DOM object, only to the javascript data structure.
Thus, since .data() never writes the data to the DOM object, there can't be any of these types of circular references that some browsers have trouble with.
There are some other useful things to know about the .data() data structure. When you use jQuery's .remove() to remove elements from the DOM or when you call $(elem).html("new html"), jQuery clears the .data() data on any removed items. This is one case where it's good not to mix jQuery with plain javascript. If you're using .data(), then you should always remove items from the DOM using jQuery functions so .data() is cleaned up appropriately. Otherwise, you can get memory leaks this way (both the .data() data can leak and any removed DOM objects that are referenced in the .data() can leak. But, if you only use jQuery methods for removing items from the DOM (including the replacing of innerHTML), then jQuery will clean things up appropriately and there will be no leaks.
So, for example, this will create a memory leak:
// suppose elem is a DOM element reference
// store some data in jQuery's data storage on behalf of a DOM element
$(elem).data("someKey", "someValue");
// remove DOM element with plain Javascript
elem.parentNode.removeChild(elem);
Because you removed the DOM element with plain Javascript, jQuery did not have a chance to clean up the data you previously stored. The DOM element itself will be garbage collected, but the .data() value you previously stored is now orphaned in jQuery's storage and is essentially a "leak" as it will likely never be cleared. On the other hand, if you do this:
$(elem).data("someKey", "someValue");
$(elem).remove();
Then, jQuery will see that you're removing the DOM element and will also clear the data you stored with .data().
A fairly simple way to see how it works is to create a couple line script with a non-minimized version of jQuery and then just step through a call to $(elem).data("key", "whatever") in the debugger and watch how it works.

Related

Does the jQuery.clone method have side effects?

If $dom is a jQuery element,
Can this line be removed safely if a is not used after?
var a = $dom.clone(true,true);
As the method clone the dom, I think $dom.clone(false,false) do not have side effects. If think that method cannot thrown an error too. I'm more sceptical about the clone of events. I think if the cloned element is not reattached, it doesn't have effects, but I'm not sure. Is there any event that can be propagated to detached dom event? (if yes, then I'm not sure we can removed safely that line)
https://api.jquery.com/clone/
The aim of the question is to determine if the absence of #nosideeffects is a bug in the externs definition of .clone() here : https://github.com/google/closure-compiler/blob/master/contrib/externs/jquery-1.9.js#L405
Whether the absense of that annotation is a bug in terms of the Closure Compiler's definition of "no side effects" is pretty much up to Google. :-)
In at least jQuery 1.x, calling .clone with true, true mutates jQuery's state information, information it doesn't just hold on the elements returned and associated with the jQuery object that ends up referenced by a: jQuery's data cache for the element (which is used both for "data" and for event handler information) is not stored on the element itself, it's stored in a structure within jQuery that's keyed by an expando property on the element. So it's not a truly pure function; it mutates the state of something: jQuery's data cache.
In jQuery 2.x and above, I believe the information is stored on the element itself (since 2.x and above don't support IE6 and IE7, which couldn't do GC across the DOM/JS boundary correctly).

Need some clarification about jQuery objects that don't reside in the DOM

I was trying to use browser console to do some HTML code manipulation. In console, I input
s = $('<div><span>ddd</span></div>');
s.remove('span');
Be noted the jQuery object s is not in the DOM, it only lives in the console. It turns out the span isn't removed from s. On the other hand, if <div><span>ddd</span></div> was in HTML, the span will surely be removed.
This brings up a question that has been confusing me for a long time. If I understand right, using $(), I can turn many things into jQuery objects, even if they are not actually in the DOM. But what is the difference between this kind of jQuery objects and the those jQuery objects that are linked to some DOM elements? And in the above code, in order to actually remove the span and get an output as <div></div>, do I have to write it into DOM?
It's because you are not using $.fn.remove properly. Correct way to remove child span is to find it first:
s = $('<div><span>ddd</span></div>');
s.find('span').remove();
When you provide a selector to remove, jQuery filters supplied collection by this selector. However, in your case there is nothing that can be filtered out, since clearly div is not span. So s.remove('span'); removes nothing.

Replace whole DOM in javascript

I need to implement undo-redo functionality in my project. But it is very complex and every change affects on many elements. I think saving and restoring whole page will be the best choice. But i have some problems with missing .data() params of DOM elements. I use next functions:
// save
var documentCopy = document.documentElement.cloneNode(true);
// restore
document.replaceChild(
documentCopy,
document.documentElement
);
How I can save and restore whole DOM with saving jQuery.data() of elements?
The trivial thing that I would try is using jQuery's clone instead. Be sure to use it with two true parameters, but be careful as this may be very very slow. Are you sure this is the only way to achieve what you want? Can't you replace a smaller portion of the document?
Note that this doesn't seem to work well with document.documentElement, and that using it with the document's body seems to lose the data on the original elements (say what?). Here's a small test.

jQuery ID Selector ("#id") Returns Array

I'm using jQuery v1.6.1 in noConflict mode.
I'm using id selectors such as $j("#divID").value to get values of stored items.
Unfortunately, $j("#inputID") is returning a list of items, so I have to use the $j("divID")[0].value to get the value of the object. The [0] seems unnecessary, since there is, by definition, only one html element with any given id.
Is this the appropriate way to gets values from an IDed object? Or is there a better way?
Thanks!
$j("#divID").val() will work just fine.
Per the jQuery documentation, .val() will return the value of the first element in the set of matched elements.
It's worthwhile understanding conceptually how jQuery works in order to see why it works this way. The result of any selector query is a jQuery object. It's that jQuery object that contains the myriad of methods that jQuery offers. .val() is one of those methods as are things like .fadeIn(), .hide(), etc... Those methods are not methods on a DOM object, but methods of a jQuery object. Because jQuery objects are general purpose and can hold 0, 1 or more DOM objects in their internal array, you get the same jQuery object back from a jQuery selector call whether the results have 0, 1 or more DOM objects in it.
Thus $j("#divID") that contains only one object returns the same type of object as $j(".rows") which might contain hundreds of DOM objects. This vastly simplifies jQuery programming because you don't have to do things differently depending upon how many objects come back from the selector query.
When you refer to $j("divID")[0], you are reaching into the jQuery object's internal array of DOM objects (that was populated on the selector query) and fetching the first DOM object in that array. At that point, you have a normal DOM object, not a jQuery object and you can use normal DOM methods or attributes on it. Occasionally this is required (to fetch the actual DOM object), but usually, it's easier to just use the methods that jQuery provides on the jQuery object. There are lots of advantages to using them such as you can chain multiple requests to most methods and it will iterate over all the DOM objects in it's internal array for you automatically.
For example, you you called this: $j("rows-even").hide() and there were 20 rows with that class, then all of them would each be operated on by the hide() method with no more code than this. Of you could chain multiple methods together like this: $j("rows-even").slideUp().slideDown(). In this case, you're running an animation and jQuery will chain these two animations together, automatically starting the second one when the first one finishes. It's all pretty useful in many circumstances and can save a ton of code over what would normally have to be written using plain JS.
$j("#divID") returns a jQuery object. In order to get the value of the selected element you have to call its val method to get the value.
Use $j("#divID").val();

__defineSetter__ on innerHTML stops it from rendering

i'm trying to create a watch method for HTML elements, using __define[GS]etter__ when a property is changed. It reacts just fine when i set the value, but if the property listened to, is innerHTML, it somehow fails to render the given string. So basically, when im adding something to innerHTML it doesn't show.
Im using the watch method described in this previous question:
Watch for object properties changes in JavaScript
I could of cause just not listen to innerHTML changes, but i'm also wondering if the __defineSetter__ somehow prevents original handling of setting the value.
Thanks!
That shim code doesn't actually write-through: when you set a property, the value is only remembered on the watch-wrapper and not passed down to the underlying object. It's designed for pure JavaScript objects whose properties have no side-effects (like changing the DOM, in innerHTML's case).
Making it write-through would be a pain since there's no way to directly call the prototype's setter. You'd have to temporarily remove the property, write to the underlying property, then put it back in place.
However it's not really worth pursuing IMO. DOM Nodes are permitted to be ‘host objects’, so there is no guarantee any of the native-JavaScript object-property functions will work on them at all.
(In any case, adding new members onto the Object prototype is generally considered a Really Bad Idea.)
I could of cause just not listen to innerHTML changes
I think that's best, yes.
Ok update.
I found this page on MSDN, which has exactly what i need:
http://msdn.microsoft.com/en-us/library/dd229916(VS.85).aspx
But the Object.getOwnPropertyDescriptor way of doing things apparently only works in IE. Bummer. Any ideas would be appreciated.
I found another webpage with something that looked interesting, but i couldn't quite get it to do what i wanted:
http://www.refactory.org/s/recent/tag/accessors
I've decided to find a workaround for now, something with a timer that checks the attribute for changes and attribute change evnets for browsers that support that.
But if any one finds a solution for the question plz post :)

Categories

Resources