186

I need to do some experiment, and I need to use some kind of unique identifier for objects in JavaScript so I can see if they are the same.

I don't want to use equality operators. Instead, I need something like the id() function in Python.

Does something like this exist?

9
  • 3
    I'm a bit curious, why you want to avoid the equality operators? Commented Jan 4, 2010 at 5:42
  • 48
    because I want something simple, and I want to see a number, something clear. This language makes kitten cry, I am already fighting it enough. Commented Jan 4, 2010 at 5:45
  • 5
    The strict equality operator (===) will do what you ask for on objects (if you're comparing numbers/strings/etc it's not the same thing), and is simpler than building a secret unique ID into every object. Commented Jan 4, 2010 at 6:31
  • 11
    @CMS @Ben Having unique ids can be useful for debugging or implementing things like an IdentitySet. Commented Jan 4, 2010 at 6:45
  • 12
    I wish to communicate that I did a breakthrough in the understanding of javascript. kind of closed a synapse. Now everything is clear. I've seen things. I gained a level in javascript programmer. Commented Jan 4, 2010 at 6:48

14 Answers 14

98

So far as my observation goes, any answer posted here can have unexpected side effects.

In ES2015-compatible enviroment, you can avoid any side effects by using WeakMap.

const id = (() => {
  let currentId = 0;
  const map = new WeakMap();

  return (object) => {
    if (!map.has(object)) {
      map.set(object, ++currentId);
    }

    return map.get(object);
  };
})();

const foo = {}
const bar = {
  baz: 1
}

console.log(id(foo))
console.log(id(bar))
console.log(id(foo))
console.log(id(bar))

Sign up to request clarification or add additional context in comments.

8 Comments

Why not return currentId?
Why WeakMap as opposed to Map? That function will crash if you provide it with a string or number.
@NateSymer by using a Map, the keys hold strong references to the objects you pass into it. This prevents them from being garbage collected and is a major memory leak
I think this should be the way to go because it gives you more control for which objects you actually do want to have an ID. wrote a blog post about this topic as well developapa.com/object-ids
This answer would be better if it explained how this solution avoids the others’ problems. You can try it on Object.prototype or Object.seal({}), for example.
|
88

Update My original answer below was written 6 years ago in a style befitting the times and my understanding. In response to some conversation in the comments, a more modern approach to this is as follows:

(function() {
    if ( typeof Object.id != "undefined" ) return;

    var id = 0;

    Object.id = function(o) {
        if ( typeof o.__uniqueid != "undefined" ) {
            return o.__uniqueid;
        }

        Object.defineProperty(o, "__uniqueid", {
            value: ++id,
            enumerable: false,
            // This could go either way, depending on your 
            // interpretation of what an "id" is
            writable: false
        });

        return o.__uniqueid;
    };
})();

var obj = { a: 1, b: 1 };

console.log(Object.id(obj));
console.log(Object.id([]));
console.log(Object.id({}));
console.log(Object.id(/./));
console.log(Object.id(function() {}));

for (var k in obj) {
    if (obj.hasOwnProperty(k)) {
        console.log(k);
    }
}
// Logged keys are `a` and `b`

If you have archaic browser requirements, check here for browser compatibility for Object.defineProperty.

The original answer is kept below (instead of just in the change history) because I think the comparison is valuable.


You can give the following a spin. This also gives you the option to explicitly set an object's ID in its constructor or elsewhere.

(function() {
    if ( typeof Object.prototype.uniqueId == "undefined" ) {
        var id = 0;
        Object.prototype.uniqueId = function() {
            if ( typeof this.__uniqueid == "undefined" ) {
                this.__uniqueid = ++id;
            }
            return this.__uniqueid;
        };
    }
})();

var obj1 = {};
var obj2 = new Object();

console.log(obj1.uniqueId());
console.log(obj2.uniqueId());
console.log([].uniqueId());
console.log({}.uniqueId());
console.log(/./.uniqueId());
console.log((function() {}).uniqueId());

Take care to make sure that whatever member you use to internally store the unique ID doesn't collide with another automatically created member name.

8 Comments

@Justin Adding properties to Object.prototype is problematic in ECMAScript 3 because these proprieties are enumerable on all objects. So if you define Object.prototype.a then "a" will be visible when you do for (prop in {}) alert(prop); So you have to make a compromise between augmenting Object.prototype and being able to iterate through record like objects using a for..in loop. This is a serious problem for libraries
There is no compromise. It's long been considered a best practice to always use object.hasOwnProperty(member) when using a for..in loop. This is a well documented practice and one that is enforced by jslint
neither of i would recomend to do this also, at least not for every object, you can do the same just with the objects you handle. unfortunately most of the time we have to use external javascript libraries, and unfortunately not every one of them are well programed so avoid this unless you have total control of all of the libraries included in your web page, or at least you know they handled well.
@JustinJohnson: There is a compromise in ES5: defineProperty(…, {enumerable:false}). And the uid method itself should be in the Object namespace anyway
Since Object.id is a function that needs to be called manually, there's no reason to put it on the global Object that we don't own, which, in my opinion, is no better than on the Object.prototype.
|
35

Latest browsers provide a cleaner method for extending Object.prototype. This code will make the property hidden from property enumeration (for p in o)

For the browsers that implement defineProperty, you can implement uniqueId property like this:

(function() {
    var id_counter = 1;
    Object.defineProperty(Object.prototype, "__uniqueId", {
        writable: true
    });
    Object.defineProperty(Object.prototype, "uniqueId", {
        get: function() {
            if (this.__uniqueId == undefined)
                this.__uniqueId = id_counter++;
            return this.__uniqueId;
        }
    });
}());

For details, see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty

4 Comments

"Latest browsers" apparently doesn't include Firefox 3.6. (Yes I'm opting out of the upgrade race in the newer versions of Firefox, and I'm sure I'm not the only one. Besides, FF3.6 is only 1 year old.)
1 year old isn't "only" in this sport. The web evolves dynamically - that's a good thing. Modern browsers have automatic updaters precisely for the purpose of allowing that.
A few years later, this seems to be working better for me than the accepted answer.
Just make sure never to write Object.prototype.uniqueId
13

Actually, you don't need to modify the object prototype and add a function there. The following should work well for your purpose.

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;
}

3 Comments

nocase, snake_case and camelCase have all three made their way into a 6-line code snippet. You don't see that everyday
this seems like a much more convincing mechanism than object hacks. you need only run it when you want the OP's object matching and it stays out of the way the rest of the time.
But if you copy the object (e.g. { …obj } ) you copy the obj.__obj_id with it. The point would be to prevent that.
6

For browsers implementing the Object.defineProperty() method, the code below generates and returns a function that you can bind to any object you own.

This approach has the advantage of not extending Object.prototype.

The code works by checking if the given object has a __objectID__ property, and by defining it as a hidden (non-enumerable) read-only property if not.

So it is safe against any attempt to change or redefine the read-only obj.__objectID__ property after it has been defined, and consistently throws a nice error instead of silently fail.

Finally, in the quite extreme case where some other code would already have defined __objectID__ on a given object, this value would simply be returned.

var getObjectID = (function () {

    var id = 0;    // Private ID counter

    return function (obj) {

         if(obj.hasOwnProperty("__objectID__")) {
             return obj.__objectID__;

         } else {

             ++id;
             Object.defineProperty(obj, "__objectID__", {

                 /*
                  * Explicitly sets these two attribute values to false,
                  * although they are false by default.
                  */
                 "configurable" : false,
                 "enumerable" :   false,

                 /* 
                  * This closure guarantees that different objects
                  * will not share the same id variable.
                  */
                 "get" : (function (__objectID__) {
                     return function () { return __objectID__; };
                  })(id),

                 "set" : function () {
                     throw new Error("Sorry, but 'obj.__objectID__' is read-only!");
                 }
             });

             return obj.__objectID__;

         }
    };

})();

Comments

5

Typescript version of @justin answer, ES6 compatible, using Symbols to prevent any key collision and added into the global Object.id for convenience. Just copy paste the code below, or put it into an ObjecId.ts file you will import.

(enableObjectID)();

declare global {
    interface ObjectConstructor {
        id: (object: any) => number;
    }
}

const uniqueId: symbol = Symbol('The unique id of an object');

export function enableObjectID(): void {
    if (typeof Object['id'] !== 'undefined') {
        return;
    }

    let id: number = 0;

    Object['id'] = (object: any) => {
        const hasUniqueId: boolean = !!object[uniqueId];
        if (!hasUniqueId) {
            object[uniqueId] = ++id;
        }

        return object[uniqueId];
    };
}

Example of usage:

console.log(Object.id(myObject));

[Edit] If you just want a helper function, you can shorten the above as follows

let counter = 0;
const weakMap = new WeakMap();
const getObjectId = (obj) => (weakMap.get(obj) ?? (weakMap.set(obj, ++counter) && counter));

Playground here

2 Comments

This will not work with Object.seal({}), and will break once Object.prototype is fed into it.
@user3840170 you're smart, fixed using a weakMap (and while I'm writing this comment I just realize another answer is also suggesting a weakMap…)
3

jQuery code uses it's own data() method as such id.

var id = $.data(object);

At the backstage method data creates a very special field in object called "jQuery" + now() put there next id of a stream of unique ids like

id = elem[ expando ] = ++uuid;

I'd suggest you use the same method as John Resig obviously knows all there is about JavaScript and his method is based on all that knowledge.

3 Comments

jQuery's data method has flaws. See for example stackoverflow.com/questions/1915341/…. Also, John Resig by no means knows all there is to know about JavaScript, and believing that he does will not help you as a JavaScript developer.
@Tim, it has as much flaws in terms of getting unique id as any other method presented here since it does roughly the same under the hood. And yes, I believe John Resig knows a lot more than me and I should be learning from his decisions even if he's not Douglas Crockford.
AFAIK $.data doesn't work on JavaScript objects but only DOM elements.
2

I faced the same problem and here's the solution I implemented with ES6

let id = 0; // This is a kind of global variable accessible for every instance 

class Animal {
    constructor(name) {
        this.name = name;
        this.id = id++; 
    }

    foo() {} // Executes some cool stuff
}

const cat = new Animal("Catty");
console.log(cat.id) // 1

1 Comment

This only works with classes you create, so it’s not a fully general solution, but at least it’s not actively harmful.
0

I've used code like this, which will cause Objects to stringify with unique strings:

Object.prototype.__defineGetter__('__id__', function () {
    var gid = 0;
    return function(){
        var id = gid++;
        this.__proto__ = {
             __proto__: this.__proto__,
             get __id__(){ return id }
        };
        return id;
    }
}.call() );

Object.prototype.toString = function () {
    return '[Object ' + this.__id__ + ']';
};

the __proto__ bits are to keep the __id__ getter from showing up in the object. this has been only tested in firefox.

2 Comments

Just keep in mind that __defineGetter__ is non-standard.
Also, modifying Object.prototype is a big no-no. This will break every library that depends on the precise behaviour of Object.prototype.toString, of which there are surprisingly many. Look for Object.prototype.toString.call on GitHub.
0

Notwithstanding the advice not to modify Object.prototype, this can still be really useful for testing, within a limited scope. The author of the accepted answer changed it, but is still setting Object.id, which doesn't make sense to me. Here's a snippet that does the job:

// Generates a unique, read-only id for an object.
// The _uid is generated for the object the first time it's accessed.

(function() {
  var id = 0;
  Object.defineProperty(Object.prototype, '_uid', {
    // The prototype getter sets up a property on the instance. Because
    // the new instance-prop masks this one, we know this will only ever
    // be called at most once for any given object.
    get: function () {
      Object.defineProperty(this, '_uid', {
        value: id++,
        writable: false,
        enumerable: false,
      });
      return this._uid;
    },
    enumerable: false,
  });
})();

function assert(p) { if (!p) throw Error('Not!'); }
var obj = {};
assert(obj._uid == 0);
assert({}._uid == 1);
assert([]._uid == 2);
assert(obj._uid == 0);  // still

Comments

0

If you came here because you deal with class instances like me you can use static vars/methods to reference instances by a custom unique id:

class Person { 
    constructor( name ) {
        this.name = name;
        this.id = Person.ix++;
        Person.stack[ this.id ] = this;
    }
}
Person.ix = 0;
Person.stack = {};
Person.byId = id => Person.stack[ id ];

let store = {};
store[ new Person( "joe" ).id ] = true;
store[ new Person( "tim" ).id ] = true;

for( let id in store ) {
    console.log( Person.byId( id ).name );
}

1 Comment

An engine new enough to implement classes is new enough to implement Map and WeakMap (and Set and WeakSet): all were introduced in ECMAScript 2015 (6th Edition), making this solution redundant.
0

Here's a variant of Justin Johnson's answer that provides a scalability benefit when you are creating billions of objects for which you want the ID.

Specifically, rather than solely using a 1-up counter (that might overflow the representational limits of Number, and can't be cycled without risking reusing an ID), we register the object and its newly generated ID with a FinalizationRegistry, such that, at some point after the object is garbage collected, the ID is returned to a freelist for reuse by a newly created object (Python's id function can also return the same ID for multiple objects, so long as the existence of the two objects does not overlap in time).

Limitations:

  1. It only works on objects, not JS primitives (this is somewhat reasonable; unlike Python, where everything is an object, JS primitives typically aren't, and the id function logically only works on objects, since primitives need not "exist" in any reasonably identifiable way).
  2. If the code creates (without discarding) billions of objects, asks for their IDs, then releases them all at once and never asks for an ID again, the recovered IDs in the freelist constitute a memory leak of sorts. Hopefully the JS optimizer stores them efficiently, so the cost remains a small fraction of what the objects themselves cost, but it's still a cost. In cases where objects with IDs are regularly created and destroyed, the wasted memory is roughly tied to the maximum number of such ID-ed objects in existence at any given point in time.

If those limitations aren't a problem though, this works fairly well. I modified the testing code a bit to hand control back to the event loop (and hopefully the garbage collector) now and again while creating 10M garbage objects to ID, and on my browser, nearly half the object IDs get reclaimed for reuse; the final loop making five objects and IDing them produces IDs just above 1M, when over 2M objects had IDs generated at some point. In a realistic scenario with meaningful code executing and real async usage I'd expect better results simply because there would be more opportunities for the finalization registry to perform clean-up.

async function sleep(ms) {
    await _sleep(ms);
}

function _sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}


    (function() {
        if ( typeof Object.id != "undefined" ) return;

        var freelist = [];  // Stores previously used IDs for reuse when an object with
                            // an ID is garbage collected, so creating and dropping billions
                            // of objects doesn't consume all available IDs
        const registry = new FinalizationRegistry((freeid) => {
            freelist.push(freeid);
        });

        var id = 0;

        Object.id = function(o) {
            if ( typeof o.__uniqueid != "undefined" ) {
                return o.__uniqueid;
            }

            Object.defineProperty(o, "__uniqueid", {
                value: freelist.length ? freelist.pop() : ++id,
                enumerable: false,
                // This could go either way, depending on your 
                // interpretation of what an "id" is
                writable: false
            });
            registry.register(o, o.__uniqueid);  // Sometime after o is collected, its ID
                                                 // will be reclaimed for use by a new object

            return o.__uniqueid;
        };
    })();
    
    var obj = { a: 1, b: 1 };
    
    console.log(Object.id(obj));
    console.log(Object.id([]));
    console.log(Object.id({}));
    console.log(Object.id(/./));

    var idsum = 0; // So we do something real to prevent optimizing out code
    // Make a ton of temporary objects with IDs, handing control back to the event loop
    // every once in a while to (hopefully) see some IDs returned to the pool
    for (var i = 0; i < 1000000; ++i) {
         idsum += Object.id({c: i});
    }
    sleep(10).then(() => {
        console.log(Object.id(function() { console.log("Hey"); }));
        for (var i = 1000000; i < 2000000; ++i) {
             idsum += Object.id({c: i});
        }
        console.log(Object.id(function() { console.log("There"); }));
        sleep(10).then(() => {
            for (var i = 0; i < 5; ++i) {
                console.log(Object.id([i]));
            }
            console.log(idsum);
        });
    });

    for (var k in obj) {
        if (obj.hasOwnProperty(k)) {
            console.log(k);
        }
    }
    // Logged keys are `a` and `b`

3 Comments

Any engine having FinalizationRegistry will also have WeakMap (FinalizationRegistry only came in ECMAScript 2021, while WeakMap was already present in 2015), so using the former instead of the latter is silly. And this will not work with Object.seal({}), while WeakMap will.
@user3840170: You're missing the point. A WeakMap can map the object to its lazily assigned ID. But it won't tell you when the lazily assigned ID can be reclaimed, so you're back to the problem of IDs not being reusable in programs that allocate and release billions of IDs over time. The only way to find and reuse IDs is to scan the whole thing to find gaps, which is wildly non-performant. The whole reason I wrote this answer was to avoid that problem, by finding a way for IDs to be reclaimed and reused while keeping O(1) CPU to assign IDs. WeakMap can't do that.
Fair enough, if you insist on reusing IDs then FinalizationRegistry is necessary; I overlooked that part. Still, this is not a reason to employ parasitic properties instead of WeakMap, or to modify Object. A single call to Object.id(Object.prototype) will wreak havoc on this solution, and it will not work on non-extensible objects either.
0

You might be interested in my "CodeWhile" episode 012, on YouTube:

It discusses the following code:

[...]
(function() {

var cleanBroken;
var cleanNewId;
var newGetId;

if (typeof Object.prototype["&_"] != "undefined")
  return;
cleanBroken = (new Function("return broken;\n" + broken.toString()))();
newGetId = new Function("id", "newId", "value", "return getId;\n" + getId.toString());
cleanNewId = (new Function("broken", "id", "newGetId", "return newId;\n" + newId.toString()))(cleanBroken, 1, newGetId);
cleanNewId(Object.prototype);
return;

function broken() { throw new Error("\"&_\" seems to be broken for this value"); }

function getId()
  {
    if (arguments.length != 0)
      throw new Error("Takes no arguments");
    if (this["&_"] !== getId && typeof this["&_"] == "function")
      return this["&_"].call(this);
    if (this === value)
      return id;
    return newId(this);
  }

function newId(value)
  {
    value["&_"] = broken;
    if (id == 0x20000000000000)
      throw new Error("Too many IDs");
    id += 1;
    value["&_"] = newGetId(id, newId, value);
    return id;
  }

})();
[...]

The general idea is that some_object["&_"]() will give a unique, numeric ID for each object and/or function. Even if the property is copied from one object/function to another, calling the utility on the destination will not yield the same ID as the source of that copy.

This code should work pretty broadly, including Microsoft Internet Explorer 6. (In that web-browser, HTML elements do not inherit from Object.prototype, but this utility can still be .call()ed on those elements.) That is, "as far back as" ECMAScript 3, if I recall correctly.

It's possible that some comments to this "answer" might result in responses that refer to the aforementioned video.

Comments

-1

This one will calculate a HashCode for each object, optimized for string, number and virtually anything that has a getHashCode function. For the rest it assigns a new reference number.

(function() {
  var __gRefID = 0;
  window.getHashCode = function(ref)
  {
      if (ref == null) { throw Error("Unable to calculate HashCode on a null reference"); }

      // already cached reference id
      if (ref.hasOwnProperty("__refID")) { return ref["__refID"]; }

      // numbers are already hashcodes
      if (typeof ref === "number") { return ref; }

      // strings are immutable, so we need to calculate this every time
      if (typeof ref === "string")
      {
          var hash = 0, i, chr;
          for (i = 0; i < ref.length; i++) {
            chr = ref.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0;
          }
          return hash;
      }

      // virtual call
      if (typeof ref.getHashCode === "function") { return ref.getHashCode(); }

      // generate and return a new reference id
      return (ref["__refID"] = "ref" + __gRefID++);
  }
})();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.