102

Do JavaScript objects/variables have some sort of unique identifier? Like Ruby has object_id. I don't mean the DOM id attribute, but rather some sort of memory address of some kind.

Nathan MacInnes
  • 11,033
  • 4
  • 35
  • 50
treznik
  • 7,955
  • 13
  • 47
  • 59

5 Answers5

72

If you want to lookup/associate an object with a unique identifier without modifying the underlying object, you can use a WeakMap:

// Note that object must be an object or array,
// NOT a primitive value like string, number, etc.
var objIdMap=new WeakMap, objectCount = 0;
function objectId(object){
  if (!objIdMap.has(object)) objIdMap.set(object,++objectCount);
  return objIdMap.get(object);
}

var o1={}, o2={}, o3={a:1}, o4={a:1};
console.log( objectId(o1) ) // 1
console.log( objectId(o2) ) // 2
console.log( objectId(o1) ) // 1
console.log( objectId(o3) ) // 3
console.log( objectId(o4) ) // 4
console.log( objectId(o3) ) // 3

Using a WeakMap instead of Map ensures that the objects can still be garbage-collected.

gitaarik
  • 42,736
  • 12
  • 98
  • 105
Phrogz
  • 296,393
  • 112
  • 651
  • 745
56

No, objects don't have a built in identifier, though you can add one by modifying the object prototype. Here's an example of how you might do that:

(function() {
    var id = 0;

    function generateId() { return id++; };

    Object.prototype.id = function() {
        var newId = generateId();

        this.id = function() { return newId; };

        return newId;
    };
})();

That said, in general modifying the object prototype is considered very bad practice. I would instead recommend that you manually assign an id to objects as needed or use a touch function as others have suggested.

Xavi
  • 20,111
  • 14
  • 72
  • 63
  • 1
    ActionScript 3 has a Dictionary object that uses strict equality for key comparison, so one can use objects as keys. Is there an equivalent in JavaScript, or would you have to manually construct unique id's for every object (either via Object.prototype or manually adding an id to select objects)? – Triynko Aug 06 '13 at 06:30
  • Unfortunately javascript doesn't have anything like that. Sounds like you will have to give objects id and then use those id's as keys in object, as you suggested. That said, if you really want to be clever, you could implement "id objects" by overriding the `toString` method and using them like this `id = new Id(); cache[id] = obj`. It's a little nuts but pretty interesting. Here's an article I wrote that explain this technique in more detail: http://xavi.co/articles/fun-with-tostring-in-javascript – Xavi Aug 06 '13 at 07:03
  • Okay, I just found out why. jQuery also overrides ID and somehow my page broke when I overrode it. Hah. Okay. So. I'll just avoid naming issues and cross my fingers. – Lodewijk Oct 19 '14 at 12:16
  • 2
    This example could be used as a great usage example for closures and prototypal inheritance. – Peter Clause Jun 01 '15 at 14:27
11

Actually, you don't need to modify the object prototype. The following should work to 'obtain' unique ids for any object, efficiently enough.

var __next_objid=1;
function objectId(obj) {
    if (obj==null) return null;
    if (obj.__obj_id==null) obj.__obj_id=__next_objid++;
    return obj.__obj_id;
}
KalEl
  • 8,978
  • 13
  • 47
  • 56
  • 8
    Just be aware that this will NOT play nicely with most ways of copying objects, if you expect the objects to have different ids afterwards. – Paul Draper Jun 09 '13 at 01:15
  • Indeed, you'd need a special "copyObject" function that takes this __obj_id into account specifically. You'd also have to be sure that there are no conflicts with the use of "__obj_id" in other libraries. This is so much easier in ActionScript, whose Dictionary object uses strict equality comparison on keys, including objects used as keys. In fact, you could probably write a Dictionary class in JS that automatically attaches ids this way to objects that are added to it as keys. It's like quantum mechanical JavaScript IDs; they don't exist until you try to observe them with this function, haha. – Triynko Aug 06 '13 at 06:38
2

I've just come across this, and thought I'd add my thoughts. As others have suggested, I'd recommend manually adding IDs, but if you really want something close to what you've described, you could use this:

var objectId = (function () {
    var allObjects = [];

    var f = function(obj) {
        if (allObjects.indexOf(obj) === -1) {
            allObjects.push(obj);
        }
        return allObjects.indexOf(obj);
    }
    f.clear = function() {
      allObjects = [];
    };
    return f;
})();

You can get any object's ID by calling objectId(obj). Then if you want the id to be a property of the object, you can either extend the prototype:

Object.prototype.id = function () {
    return objectId(this);
}

or you can manually add an ID to each object by adding a similar function as a method.

The major caveat is that this will prevent the garbage collector from destroying objects when they drop out of scope... they will never drop out of the scope of the allObjects array, so you might find memory leaks are an issue. If your set on using this method, you should do so for debugging purpose only. When needed, you can do objectId.clear() to clear the allObjects and let the GC do its job (but from that point the object ids will all be reset).

Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
Nathan MacInnes
  • 11,033
  • 4
  • 35
  • 50
  • I think this is a slow-but-robust solution, and can be improved a little: ``` var objectId = (function() { var mem = []; var f = function(obj) { var r = mem.indexOf(obj); if (r === -1) { r = mem.length; mem.push(obj); } return r; }; f.reset = function() { return mem = []; }; return f; })(); ``` – luochen1990 Jul 04 '15 at 08:15
  • @luochen1990, I think you'd be surprised at the speed of it. (But you're right, it's better practice to put the indexOf() result in a variable, although I would argue it from a DRY perspective rather than optimisation.) As long as the GC issue can be effectively managed, I think you'd have to have a helluva lot of objects to notice any significant performance impact. – Nathan MacInnes Jul 07 '15 at 11:36
0

const log = console.log;

function* generateId() {
  for(let i = 0; ; ++i) {
    yield i;
  }
}

const idGenerator = generateId();

const ObjectWithId = new Proxy(Object, {
  construct(target, args) {
    const instance = Reflect.construct(target, args);
    instance['id'] = idGenerator.next().value;
    return instance;
  }
})

const myObject = new ObjectWithId({
  name: '@@NativeObject'
});

log(myObject.id);