21

I generally avoid having to include the jQuery library in my scripts, however I only recently came across jQuery $.data()'s functions ability to associate data, in the form of key value pairs, objects & even functions, with any element.

From what I've read, jQuery's $.data() function also has built in safeguards that prevent memory leakage associated with such practices, but it's overkill to include the entire JQ library for that single function.

Does anyone know of a native alternative?

EDIT To make myself more clear, I'm not seeking the native function to retrieve element attributes. jQuery's $.data() API goes far beyond such use, extending to its ability to associate javascript objects & functions with jQuery element nodes.

This article (http://tutorialzine.com/2010/11/jquery-data-method/) touches on this usage, but as an example I currently am using it to associate a GSAP Timeline animation with an object, so that I can access and call GSAP Timeline's .reverse() animation function outside of the function which it is created. For example:

function doAnimation(){
    var element = document.createElement('div'),
        timeline = new TimelineMax({....GSAP related fns...}),
        options = {
            ....
            timeline: timeline
        };
   $(element).data('options', options);
}


function reverseAnimation($element){
    var options = $element.data('options'),
        previouslyCreatedTimeline = options.timeline;

    previouslyCreatedTimeline.reverse();
}

Maybe not the clearest example if you aren't a user of GSAP, but in essence, the $.data() method allowed me to associate a javascript object with an element, so that I can access it's methods in a function outside of its original scope.

AJeezy9
  • 1,159
  • 2
  • 11
  • 15
  • possible duplicate of [Set data attribute using JavaScript](http://stackoverflow.com/questions/11286661/set-data-attribute-using-javascript) – DrCord Mar 23 '15 at 23:13
  • No, not a duplicate. Although jquery's data function method is primarily used for data attributes presently on an element, I'm speaking of its use to associate it with actual javascript objects – AJeezy9 Mar 23 '15 at 23:22
  • Why can't you just do something like `document.getElementById("foo").bar = { baz: function() {} }`? – Salman A Mar 23 '15 at 23:46
  • @SalmanA : Because the elements I'm using this with are being dynamically created with unique Ids. – AJeezy9 Mar 23 '15 at 23:49

6 Answers6

16

I've written a wrapper around WeakMap and Map, that should do the job. The nice thing about WeakMap is, that the value gets GarbageCollected, once the Element is deleted. This should avoid memory leaks.

/** A storage solution aimed at replacing jQuerys data function.
 * Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap].
 * This makes sure the data is garbage collected when the node is removed.
 */
window.dataStorage = {
    _storage: new WeakMap(),
    put: function (element, key, obj) {
        if (!this._storage.has(element)) {
            this._storage.set(element, new Map());
        }
        this._storage.get(element).set(key, obj);
    },
    get: function (element, key) {
        return this._storage.get(element).get(key);
    },
    has: function (element, key) {
        return this._storage.has(element) && this._storage.get(element).has(key);
    },
    remove: function (element, key) {
        var ret = this._storage.get(element).delete(key);
        if (!this._storage.get(element).size === 0) {
            this._storage.delete(element);
        }
        return ret;
    }
}

Use it like this:

var myElement = document.getElementById("myId");
dataStorage.put(myElement, "myKey", "myValue");

This is much faster than $.data(), but still a little slower than storing information as a property at an element.

htho
  • 1,549
  • 1
  • 12
  • 33
8

You could store anything on any DOM element:

// store data:
document.body.data = {a:1, b:2};
    
// retrieve data:
console.log(document.body.data);

I used the data word here as a custom DOM property, but it can be any word that is not already in use.

Or you can simply create a unique identifier on every DOM element you wish to be connected to some data, and store the actual data somewhere else, globally, using a map, for example:

// Naïve global "store" (can also use Map to generate it):
window._data = {
  "abc123": {a:1, b:2} // "abc123" is a random ID assigned to a DOM node
}

// so to automatically generate that, we can enhance all elements:
window._data = {}; // or Map
HTMLElement.prototype.setData = data => {
  // create ID if does not already exists
  this._dataId = this._dataId || generateUID() // implement your own generator

  window._data[this._dataId] = data
}

HTMLElement.prototype.getData = () => window?._data[this._dataId];

// some HTML element with data (`<body>` in this simplified example)
document.body.setData({a:1}) // setter
document.body.getData() // {a:1} (getter)

Code above was not tested by you get the idea


jQuery doesn't store things on the elements themselves, because it can lead to memory leaks, but since you need this for a very specific task, then this would work. Just make sure you know how memory leaks work before you try this.

The other answers mentioned the data HTML attribute. This has nothing to do with jQuery data. Absolutely nothing, and it also requires you to parse and stringify your data (if it's not a string already).

vsync
  • 118,978
  • 58
  • 307
  • 400
  • 1
    In hopes of not being referred to google, any quality resources you can link to about understanding memory leaks? Because I can think of many ways of storing variable data to a window, but memory leaks is the primary reason for me asking this question. I guess it's just one of those topics that I know is incredibly important for performance but still also somewhat mythy in how/when/why it occurs. – AJeezy9 Apr 25 '15 at 20:29
  • @AJeezy9 - well, if you remove an element, and that element uses the technique above, and the data on that deleted element points to another DOM element, then that might cause a memory leak. – vsync Apr 25 '15 at 20:43
  • 1
    I still do not understand why is this a memory leak, html element already have many attributes that are references to other objects, so what would be the problem of adding another one? – Mojimi Dec 04 '18 at 11:22
  • I must say I heard about possible memory leaks the first time but I'd assume this is a browser problem not "javascript problem". JS programmers should ignore the issue completely as it should be addressed by browser providers. – jakubiszon Mar 27 '19 at 09:15
  • Correct me if I'm wrong, but I think @vsync means element A stores in its `data` a reference to element B, then solely deleting element B will not release B's memory. Now if A and B both store a reference to each other, even deleting them together may trigger no GC, thus a memory leak? – wlnirvana Jun 13 '20 at 12:12
  • But still this largely depends on the browser implementation, especially considering most browsers support child and parent reference APIs, which is already a cyclic structure. They may (or may not) have been handling the issue very well. – wlnirvana Jun 13 '20 at 12:14
8

for jquery's $.data(), use .dataset

<div class='info' data-some-data='12' />
// jQuery
var info = $('.info').data('someData'); // 12

// vanilla
const info = document.querySelector('.info').dataset.someData; // 12

Source:

https://www.jamesbaum.co.uk/blether/vanilla-js-equivalent-jquery-find-parent-data-remove-class-empty-append/#data

Erdal G.
  • 2,694
  • 2
  • 27
  • 37
hberbary
  • 117
  • 1
  • 6
3

See the following generic data function. Use $.data(obj) to get all metadata attributed to any object, $.data(obj, key) to get the metadata with a given key attributed to an object, and $.data(obj, key, val) to set the metadata with a given key to an object. There may be issues with collisions of native object attributes like setting something with a key of 'toString' or 'hasOwnProperty'.

window.$ = {
    data: function(obj, key, val) {
        if(!obj) {
            return this._data;
        } else if(!key) {
            if(!(obj in this._data)) {
                return {};
            }
            return this._data[obj];
        } else if(arguments.length < 3) {
            if(!(obj in this._data)) {
                return undefined;
            }
            return this._data[obj][key];
        } else {
            if(!(obj in this._data)) {
                this._data[obj] = {};
            }
            this._data[obj][key] = val;
        }
    },
    _data: {}
};

Here's an example of how it's used:

$.data(document.body); // Returns {} because no data has been set for this object
$.data(document.body, 'lastUpdate', new Date());//Sets 'lastUpdate' of obj to current time
$.data(document.body, 'lastUpdate'); // Gets previously set time
$.data(document.body); // Gets object of all data, including 'lastUpdate' time
$.data(document.body, 'nonexistant'); // Returns undefined because property was never set
$.data(); // Returns all metadata
Pluto
  • 2,900
  • 27
  • 38
  • That solution seems to leak memory. – Bergi Mar 24 '15 at 00:12
  • This seems like the best solution. How do you know it has a memory leak? Fixing the memory leak on this would be the best solution imo. – DrCord Mar 24 '15 at 15:08
  • To avoid name collisions, it would make sense to use a `Symbol` in place of `_data` – Brian M. Hunt May 05 '16 at 15:14
  • @BrianM.Hunt I created a fiddle that shows an example of letting you easily customize the internal data store to something besides `_data`. Realistically though, people should probably modify the code I've provided before using it (especially to replace the `window.$ = { ... };` name). Here it is: https://jsfiddle.net/02qaL10m/ – Pluto May 05 '16 at 19:16
3

For simple key-value storage, use .dataset, which reflects the data-* attributes of the element. It is limited to string values however, so you would need to serialize composite values (e.g. using JSON).

To store references to objects and functions, I'd recommend to use a WeakMap. It will take care to avoid any memory leaks. In browser that don't support this, just store them as a direct property on the html element, but take care to choose collision-free property names.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

Use the setAttribute method:

document.getElementById('item1').setAttribute('data', "icon: 'base2.gif', url: 'output.htm', target: 'AccessPage', output: '1'");

But you really should be using data followed with a dash and with its property, like:

<li ... data-icon="base.gif" ...>

And to do it in JS use the dataset property:

document.getElementById('item1').dataset.icon = "base.gif";
DrCord
  • 3,917
  • 3
  • 34
  • 47
  • Appreciate your response, but please see above edit for a clearer explanation as to why this isn't the correct answer. I will search for the article that better explains this usage and link to it when I have time. – AJeezy9 Mar 23 '15 at 23:20
  • It sounds like you are asking how to do something that is built into super basic javascript...adding more data to an object...maybe you should clarify exactly what you mean by "associate data, in the form of key value pairs, objects & even functions, with any element." – DrCord Mar 23 '15 at 23:24
  • I understand the confusion. Above, I linked to an article and provided my own example use case in an attempt to help you better understand. I wish the question could have been written more clearly to avoid confusion, but couldn't think of a better way. – AJeezy9 Mar 23 '15 at 23:44