17

I'm developing code using jQuery and need to store data associated with certain DOM elements. There are a bunch of other questions about how to store arbitrary data with an html element, but I'm more interested in why I would pick one option over the other.

Say, for the sake of extremely simplified argument, that I want to store a "lineNumber" property with each row in a table that is "interesting".

Option 1 would be to just set an expando property on each DOM element (I hope I'm using the term 'expando' correctly):

$('.interesting-line').each(function(i) { this.lineNumber = i; });

Option 2 would be to use jQuery's data() function to associate a property with the element:

$('.interesting-line').each(function(i) { $(this).data('lineNumber', i); });

Ignoring any other shortcomings of my sample code, are there strong reasons why you would choose one means of storing properties over the other?

James A Mohler
  • 10,562
  • 14
  • 41
  • 65
AwesomeTown
  • 2,668
  • 2
  • 21
  • 36

4 Answers4

20

Using $.data will protect you from memory leaks.

In IE, when you assign a javascript object to an expando property on a DOM element, cycles that cross that link are not garbage collected. If your javascript object holds a reference to the dom object, the whole cycle will leak. It's entirely possible to end up with hidden references to DOM objects, due to closures, so you may leak without realizing it.

The jQuery datastore is set up to prevent these cycles from forming. If you use it, you will not leak memory in this way. Your example will not leak because you are putting primitives (strings) on the DOM element. But if you put a more complex object there, you risk leaking.

Use $.data so you won't have to worry.

Xavi
  • 19,255
  • 13
  • 68
  • 63
Sean McMillan
  • 9,718
  • 5
  • 52
  • 62
  • It is certainly a lot less necessary in 2017 than it was in 2011! I _think_ that IE8 has the same issue, and it's fixed in IE9, but I really don't know. – Sean McMillan Dec 27 '17 at 20:03
  • It'd be really practical to attach data directly to elements. Much simpler than tracking down jQuery's memory leaks caused by `$.data`. – thorn0 Dec 27 '17 at 20:13
  • You've got it backwards. oldIE leaks memory when you attach data directly to elements. `$.data` protects you from those memory leaks. – Sean McMillan Dec 28 '17 at 19:28
  • 1
    This protection is itself prone to memory leaks. See https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery – thorn0 Dec 29 '17 at 09:28
  • Wow! It turns out `$.data` has been re-implemented to use direct data attachment. So both jQuery 2.2.4 and 3.x are safer than their predecessors when it comes to memory leaks. https://github.com/jquery/jquery/issues/1734 – thorn0 Dec 29 '17 at 10:04
  • Good research, @thorn! Looks like in jQuery 2, when they changed to only support IE9+, they dropped the cache, and data properties are stored directly on the DOM element. So If you're using jQuery 2+, and on any modern browser, there's no danger of leaks either way. – Sean McMillan Dec 29 '17 at 15:19
  • 1
    They did it in 2.2.0. So it's jQuery 2.2+ that is safe for use with things like SlickGrid (see https://github.com/mleibman/SlickGrid/issues/855). – thorn0 Dec 29 '17 at 20:19
13

If you are authoring a plugin you should use $.data. If you need to store the attribute often and rarely need to query the DOM for it then use $.data.

Update 5 years later: jQuery does not query the DOM based on expando properties set, and hasn't done so for a while. So use $.data. There's no reason to pollute the DOM when there is no pragmatic use to do so.

Robert Siemer
  • 26,279
  • 9
  • 72
  • 84
Crescent Fresh
  • 107,974
  • 25
  • 151
  • 138
2

Using $.data doesn't modify the DOM. You should use $.data. If you're creating a plugin then you should store one object in $.data with properties on that object as opposed to storing each of those properties as different key/value pairs in the $.data structure.

Ken Browning
  • 27,173
  • 6
  • 54
  • 67
  • 2
    note that that's how the $.data repositories work, so you're just adding one extra indirection level. if you advice on this because of namespace isolation, it can just as effectively done with a plugin-specific prefix. granted, this would only be significant if storing lots of data and/or accessing it on a tight loop. anywhere else, it's a matter of style preference – Javier Jul 21 '09 at 16:23
  • 1
    Yeah, I'd avoid modifying the DOM. IIRC, it can cause some pretty bad memory leaks on some browsers. – seth Jul 21 '09 at 16:32
  • @Javier: I was relaying what the documentation for $.data advises. I agree that the extra layer of indirection doesn't seem all that valuable but docs state its best practice. – Ken Browning Jul 21 '09 at 16:39
0

Let me rephrase the question: What are the practical differences between the two data binding options available?

Actually there are three options:

$(e).prop('myKey', myValue);
$(e).data('myKey', myValue);
$(e).attr('data-myKey', myValue);

Note: OP’s e.myKey = myValue is practically the same as the .prop() line.

  • if you need more than strings, use .prop(), i.e. expando properties
  • if you need DOM/CSS transparency and/or HTML serialization use .attr('data-*')
  • if you need both you are out of luck
  • if you only use strings, but need no DOM, read on to weigh pros and cons yourself
  • what is with .data() → read the last two paragraphs

If you ever want to pass the data around with serialized HTML you need the .attr() solution. I.e. whenever you use things like .innerHTML or .html() or want to construct snippets from strings with data included. The same applies if you want to use CSS selectors like elem[data-myKey]. Disadvantage: you can only store strings.

If you don’t need your data to be visible in the DOM or available to CSS interaction .data() and .prop() might work. Their biggest advantage is: they can hold any Javascript value.

.prop() biggest disadvantage is the possibility of name collision. Only pick names you can be sure will not be used as native property ever. E.g. scope as key is a bad idea, because it exists on some HTML elements...

Now comes .data(). The other answers seem to swear on it, I avoid it. The memory leaks related to .prop() and expando properties in general belong to the past, so that is no advantage any more. But you will be secured against name collisions with HTML properties. That is an advantage. But you get a bunch of disadvantages:

$(e).data('myKey') draws its uninitialized value from the data-myKey attribute if available, runs JSON.parse() on those and sometimes returns that or falls back to the string value of the attribute. Once you set $(e).data('myKey', myValue) you lose the relationship with the data-myKey attribute, which nevertheless lives on with its “old” value, shown in DOM and in CSS interactions. On top, the key name you use is subject to possible name mangling. I.e. if you ever decide to read all key-value via $(e).data() the keys in that object might be different.

Because of this erratic behavior (mixing expando property technology with data-* attributes) and inconsistent get/set design I always avoid .data().—Fortunately that is easy to do with .prop() and .attr() (with data-* keys for compliance).

If you really want to use .data() to avoid name clashes with native properties, my advice: do not mix with data-* attributes, consider them a different thing, and avoid name clashes with those.—Does that make sense? For automatic clash avoidance you have to avoid clashes elsewhere manually. Great design.

Robert Siemer
  • 26,279
  • 9
  • 72
  • 84