169

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?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Stefano Borini
  • 138,652
  • 96
  • 297
  • 431
  • 3
    I'm a bit curious, why you want to avoid the equality operators? – Christian C. Salvadó Jan 04 '10 at 05:42
  • 39
    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. – Stefano Borini Jan 04 '10 at 05: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. – Ben Zotto Jan 04 '10 at 06:31
  • 10
    @CMS @Ben Having unique ids can be useful for debugging or implementing things like an IdentitySet. – Alex Jasmin Jan 04 '10 at 06:45
  • 1
    @Ben, actually both `==` and `===` behave the same for object references... see the step 13 of both algorithms: http://bclary.com/2004/11/07/#a-11.9.3 http://bclary.com/2004/11/07/#a-11.9.6 – Christian C. Salvadó Jan 04 '10 at 06:46
  • 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. – Stefano Borini Jan 04 '10 at 06:48
  • ummm, how can JS do a strict comparison between objects without a unique id, that's impossible, I don't get it – Alexander Mills Feb 16 '15 at 08:08
  • 2
    I came here from google because I need to use object identifiers as map indexes - in C++, you'd use pointers, making for example map of ``. So in JS, I wanted to make array that will form unique key for every object, so that `myArray[{}]` does allways access new location. – Tomáš Zato Sep 29 '15 at 21:38
  • @AlexanderMills, they compare reference address, probably – Rodrigo Rodrigues Jul 13 '19 at 22:51

13 Answers13

86

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.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Justin Johnson
  • 30,978
  • 7
  • 65
  • 89
  • 2
    @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 – Alex Jasmin Jan 04 '10 at 22:06
  • 30
    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 – Justin Johnson Jan 04 '10 at 23:01
  • 3
    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. – useless Mar 14 '11 at 23:32
  • 1
    @JustinJohnson: There is a compromise in ES5: `defineProperty(…, {enumerable:false})`. And the `uid` method itself [should be in the `Object` namespace anyway](http://stackoverflow.com/q/13239317/1048572) – Bergi Dec 18 '12 at 22:16
  • @JustinJohnson if lets say the object id was `4` how would you then assign obj with the id 4 to a variable so you can do stuff with it...like acess its properties? – Sir Jan 17 '15 at 09:20
  • @JustinJohnson I agree that enumeration should be guarded, but how would you prevent `__uniqueid` from enumerating? `var x = {a:0,b:1}; for(var i in x) if(x.hasOwnProperty(i)) console.log(i); /* so far so good: a, b */ x.uniqueId(); for (var i in x) if (x.hasOwnProperty(i)) console.log(i); /* whops: a, b, __uniqueid */` Bergi's code helps, but not pre-ES5. – TWiStErRob Feb 09 '16 at 13:24
  • FWIW, this also works when you replace `==` with `===` (as you might do if you need to convert this to Coffeescript). – Pistos Aug 19 '16 at 15:09
  • 2
    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`. – Emile Bergeron May 21 '19 at 21:56
84

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

id({}); //=> 1
hakatashi
  • 9,006
  • 2
  • 22
  • 22
  • 2
    Why not `return currentId`? – Gust van de Wal Nov 06 '19 at 13:42
  • Why `WeakMap` as opposed to `Map`? That function will crash if you provide it with a string or number. – Nate Symer Nov 20 '19 at 19:09
  • 14
    @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 – Downgoat Aug 18 '20 at 08:35
  • 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 https://developapa.com/object-ids/ – Nicolas Gehlert Oct 03 '22 at 13:40
  • 1
    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. – user3840170 Apr 12 '23 at 14:44
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

bignose
  • 30,281
  • 14
  • 77
  • 110
Aleksandar Totic
  • 2,557
  • 25
  • 27
  • 2
    "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.) – bart Sep 28 '11 at 08:06
  • 8
    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. – Kos Nov 24 '12 at 09:02
  • 1
    A few years later, this seems to be working better for me than the accepted answer. – Fitter Man Feb 14 '15 at 23:02
  • Just make sure never to write `Object.prototype.uniqueId`… – user3840170 Apr 12 '23 at 13:32
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;
}
KalEl
  • 8,978
  • 13
  • 47
  • 56
  • 18
    nocase, snake_case *and* camelCase have all three made their way into a 6-line code snippet. You don't see that everyday – Gust van de Wal Nov 06 '19 at 13:45
  • 4
    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. – JL Peyret Feb 13 '20 at 17:38
  • 1
    But if you copy the object (e.g. { …obj } ) you copy the obj.__obj_id with it. The point would be to prevent that. – Gábor Angyal Dec 20 '21 at 05:21
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__;

         }
    };

})();
Luc125
  • 5,752
  • 34
  • 35
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

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133
  • This will not work with `Object.seal({})`, and will break once `Object.prototype` is fed into it. – user3840170 Apr 12 '23 at 15:06
  • @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…) – Flavien Volken Apr 13 '23 at 07:35
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.

vava
  • 24,851
  • 11
  • 64
  • 79
  • 6
    jQuery's `data` method has flaws. See for example http://stackoverflow.com/questions/1915341/whats-wrong-with-adding-properties-to-dom-element-objects/1915699#1915699. 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 Down Jan 04 '10 at 09:51
  • 1
    @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. – vava Jan 04 '10 at 10:01
  • 1
    AFAIK [$.data](http://api.jquery.com/jquery.data/) doesn't work on JavaScript objects but only DOM elements. – mb21 Jun 12 '14 at 11:07
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
user3840170
  • 26,597
  • 4
  • 30
  • 62
Jaime Rios
  • 1,956
  • 2
  • 15
  • 20
  • 1
    This only works with classes you create, so it’s not a fully general solution, but at least it’s not actively harmful. – user3840170 Apr 12 '23 at 15:28
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.

Eric Strom
  • 39,821
  • 2
  • 80
  • 152
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
Klortho
  • 613
  • 1
  • 6
  • 12
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 );
}
Juergen Riemer
  • 1,393
  • 2
  • 13
  • 29
  • 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. – user3840170 Apr 12 '23 at 16:37
-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++);
  }
})();
Ivan Sanz Carasa
  • 1,141
  • 8
  • 16
-1

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 cleanup.

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`
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 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 Apr 12 '23 at 15:15
  • @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. – ShadowRanger Apr 12 '23 at 16:27
  • 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. – user3840170 Apr 12 '23 at 17:02