6

Underneath the hood JQuery uses a map of "UUIDs" (just a counter it maintains as jQuery.uuid) to work around the well-known memory leak issues browsers have when you attach a property to a tag in the DOM from Javascript. In lieu of doing so, JQuery uses the $.data(tag, name, value) to store data in a map keyed off of the uuid (a key that can be determined by checking tag[jQuery.expando]).

While $.data() is very useful, there are times you want to map data to tags without dumping that data into one global bucket - you want your own smaller bucket of data that you can, for example, check the length of or loop through.

As a contrived example, suppose you have icons that rotate through one of 4 states when clicked. When one is in state 2, you want to add it to an array of icons in state 2. The most obvious way to do so is to add the tag to an array; however doing so would create a memory leak. You could call $.data() on the checkbox, but that doesn't quite accomplish what you're trying to do - you'd have to loop through all the checkboxes checking $.data() against them to figure out which are and aren't in the list.

You need to store some abstraction of the tags in an array, and that's jQuery's UUIDs. You could write your own UUID functionality, but ideally you just leverage the UUID functionality already built-in to JQuery for both code-size and quality reasons. You could ask JQuery to attach a UUID to the tag implicitly by calling $.data(tag, 'irrelevant', 1) and then check tag[jQuery.expando] to get its UUID, and finally use that in the list... but that's a bit of a hack. Really what would be ideal is to have the following exposed in the public API:

$.getUuid(tag): Checks for and creates a UUID if none exists - ideally the method is factored out of $.data() and creates or fetches the uuid for the tag passed in.

So, is there a reason this isn't factored out into its own method in jQuery? Is this harmful in some way? Was it just never something that seemed useful?

I should note that I've actually factored it out in the version of jQuery we're using, and it's very helpful. But perhaps there's an underlying risk I'm not hitting in my usage. I'm also aware of a plugin that sort-of accomplishes this, but it's a bit broken - and having 2 codepaths to perform the same UUID functionality is both a bit wasteful and a bit brittle.

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
Chris Moschini
  • 33,398
  • 18
  • 147
  • 176
  • 1
    I think this is a great question, but it might be more effective to just email it to a jQuery maintainer :-) Or, log a bug - they really do take the bug list seriously. – Pointy Aug 11 '11 at 22:23
  • 2
    I think they'll have to rename that feature before exposing it to the outside world: sequential integers are everything but universally unique identifiers. – Frédéric Hamidi Aug 11 '11 at 22:39
  • @Pointy OK, I'll do so - I just really appreciate the wider Stack Overflow community and comment ranking system for questions like this. – Chris Moschini Aug 11 '11 at 23:01
  • Note that this was proposed to the JQuery team and (quite rudely) rejected, basically saying, we get a lot of requests, write your own. – Chris Moschini Mar 13 '14 at 15:54

2 Answers2

4

I think the obvious answer here is that jQuery built their uuid for internal use and hasn't seen a great reason or great demand to bother making it publicly consumable. That doesn't mean reasons don't exist, just that they haven't seemed important enough for it to make it to the top of the list of things to work on.

Monotomically increasing counters to be used as unique IDs are pretty trivial to implement and I've used one many times. I didn't feel like I needed class library support for it to do so.

I think your fear of memory leaks because you keep object references around is a bit overblown. First off, it's only a memory leak if you get rid of the object and forget to get rid of some reference to it. That's just a general rule in garbage collected languages that you have to "know" where you're keeping references to objects that you may get rid of and clean up those references when you intend for the object to be freed.

Second, it's only a meaningful memory leak if you do the same thing a lot of times per page or if the object is very, very large. They all get cleaned up when you go onto the next page so it's not like it's something that accumulates forever unless you never leave that browser page and do the same thing over and over which involves removed objects, but references that aren't removed.

Third, jQuery's .data() mechanism attempts to solve a lot of this for you when using DOM objects.

Fourth, in your contrived example, this does not create a memory leak unless you don't clean up your array of icons in state 2 when it's no longer valid or no longer being used. If you clean it up, then there's no problem with storing a direct DOM reference in that array. If you don't clean up the array, then even the array itself is a memory leak, even if it has abstract uuids in it instead of DOM references. Using abstract references is just a lot more work than needed most of the time.

Again, even if you get it to leak, the leaks are only important if the page has a long life and you are repeatedly creating and releasing objects, but not clearing all references to them in a way that the references accumulate over time and doing this enough that the memory leak they cause is meaningful. I keep references to DOM objects in JS variables all the time. I am just careful to make sure that I null them out when I no longer need them so I know that the DOM object can be freed sometime down the road.

jfriend00
  • 580,699
  • 78
  • 809
  • 825
  • I'm not certain you understand what causes browser memory leaks. For example, an array of uuids is just an array of numbers. This: var array = [1,2,3,4]; does not leak in Javascript - an array of uuids is the same as far as the browser is concerned. Now the uuid added to the tag does leak if you don't clean it up, but that's the benefit of leveraging existing jQuery code - you reuse all the well-tested code that handles that addition, tracking, and cleanup, instead of cobbling your own together. So, I realize I can write my own - it's just not a good idea. – Chris Moschini Aug 11 '11 at 23:58
  • I absolute do understand what causes browser memory leaks. I've tracked them down in real code before. What I don't understand is what you think you need to use uuids for to protect against leaks that jQuery doesn't already provide functionality for. If I understand your example, it doesn't leak. Storing a DOM object reference in an array does not, by itself, cause a leak any more than storing a uuid. It would take other circumstances like removing the DOM object from the DOM and freeing all other references to turn that into a leak and then doing that enough times to be relevant. – jfriend00 Aug 12 '11 at 00:39
  • Correct - it basically creates a situation in which a leak is likely (all it takes is a DOM removal), and I don't want to write brittle code. – Chris Moschini Aug 12 '11 at 01:20
  • Also, it may take one DOM removal to make a leak. But it would potentially take thousands of DOM removals (without corresponding JS reference cleanup) to make a leak that is significant. That can happen in some circumstances, but isn't the usual web page. – jfriend00 Aug 12 '11 at 01:38
0

This was submitted to the jQuery team and rejected.

However, you can maintain a list of tags that delegates garbage collection to jQuery like so:

http://jsfiddle.net/b9chris/Un2mH/

The essential code being:

sets[oldIndex] = sets[oldIndex].not(this);
sets[index] = sets[index].add(this);

Although this creates a separate memory burden - these methods do more than just add a tag to an array, they also maintain a stack of previous states of this collection (.pushStack() is called internally). If the page is long-lived with a lot of user actions, the collections will grow without bound. To prevent this you can hack the objects to delete the stack:

sets[oldIndex] = sets[oldIndex].not(this);
sets[oldIndex].prevObject = null;
sets[index] = sets[index].add(this);
sets[index].prevObject = null;

A waste of some CPU cycles but clean enough.

Chris Moschini
  • 33,398
  • 18
  • 147
  • 176