Web Performance Calendar

The speed geek's favorite time of year
2012 Edition
ABOUT THE AUTHOR

Stoyan (@stoyanstefanov) is a Facebook engineer, former Yahoo!, writer ("JavaScript Patterns", "Object-Oriented JavaScript"), speaker (JSConf, Velocity, Fronteers), toolmaker (Smush.it, YSlow 2.0) and a guitar hero wannabe.

HTML5 introduces custom data attributes: non-visible pieces of information associated with a DOM node. This is an extremely convenient feature, especially since these attributes are accessible by both JavaScript and CSS. But what about performance? Is it possible that the use of data attributes will make your application run slower?

Accessing data

Let’s say you have an element that looks like:

<divid="mydiv"data-purpose="greeting">Hello</div>

With HTML5 you can access the custom attribute with

mydiv.dataset.purpose; // "greeting"

For older browsers you need to fall back to using getAttribute():

mydiv.getAttribute('data-purpose'); // "greeting"

DOM is slow

You know that DOM operations are a common bottleneck in dynamic web application, more so than anything you do in ECMAScript land (functions, objects, loops). And these data attributes hang on DOM nodes. Does that mean that reading and writing them in JavaScript can be slow?

Let’s write a JSPerf.com test to find out.

DIY Data utility

In the test we compare the performance of:

  1. using HTML5 dataset
  2. using the fallback old school getAttribute() and setAttribute()
  3. using a do-it-yourself data storage using a small Data object

The Data utility creates one custom attribute __data for each DOM element we’re interested in and uses it as an auto-incremented identifier. The actual data will be stored privately in the Data object’s closure in a warehouse variable:

varData = function(){varwarehouse = {}; varcount = 1; return{reset: function(){count = 1; warehouse = {}; }, set: function(dom, data){if(!dom.__data){dom.__data = "hello" + count++; }warehouse[dom.__data] = data; }, get: function(dom){returnwarehouse[dom.__data]; }}; }();

(Side note: creating custom (expando) attributes is associated with memory leaks in IE, so it’s a good idea to cleanup all these expandos before you remove the DOM node or unload the page.)

Using this utility looks like:

Data.set(mydiv, {purpose: "greeting"}); Data.get(mydiv).purpose; // "greeting"

The test

You can run the test at jsperf.com/data-attributes.

It uses one DOM node, and writes and reads three data properties using each of the three methods: old school, dataset and DIY.

// old school attributesdiv.setAttribute('data-po', 'to'); div.setAttribute('data-ta', 'ma'); div.setAttribute('data-to', 'to'); vara = div.getAttribute('data-po'); varb = div.getAttribute('data-ta'); varc = div.getAttribute('data-to'); // HTML5 datasetdiv.dataset.po = 'to'; div.dataset.ta = 'ma'; div.dataset.to = 'to'; vardata = div.dataset; vara = data.po; varb = data.ta; varc = data.to; // DIY Data utilityData.set(div, {po: 'to', ta: 'ma', to: 'to'}); vardata = Data.get(div); vara = data.po; varb = data.ta; varc = data.to;

Results

The results show that using a simple Data utility to store data associated with a DOM node is significantly faster than storing the data in the DOM node. And the results are consistent across browsers.

Using data attributes can be anywhere between 5 to 30 times slower than using a Data object.

data

Interestingly, HTML5 dataset is the slowest, even slower than using getAttribute(), although the big difference really is between a data attribute and a Data object in ECMAScript land.

Takeaways

Using data attributes is convenient, but if you’re doing a lot of reading and writing, your application’s performance will suffer. In such cases it’s better to create a small utility in JavaScript to store and associate data about particular DOM nodes.

In this article, you saw a very simple Data utility. You can always make it better. For example, for convenience you can still produce markup with data attributes and the first time you use Data.get() or Data.set(), you can carry over the values from the DOM to the Data object and then use the Data object from then on.