identificador de objeto único en javascript

120

Necesito hacer un experimento y necesito saber algún tipo de identificador único para objetos en javascript, para poder ver si son iguales. No quiero usar operadores de igualdad, necesito algo como la función id () en Python.

Existe algo como esto ?

Stefano Borini
fuente
2
Tengo un poco de curiosidad, ¿por qué quiere evitar los operadores de igualdad?
CMS
22
porque quiero algo simple, y quiero ver un número, algo claro. Este lenguaje hace llorar al gatito, ya lo estoy luchando lo suficiente.
Stefano Borini
4
El operador de igualdad estricta (===) hará lo que pida en los objetos (si está comparando números / cadenas / etc.no es lo mismo), y es más simple que crear una ID única secreta en cada objeto.
Ben Zotto
4
@CMS @Ben Tener identificadores únicos puede ser útil para depurar o implementar cosas como un IdentitySet.
Alex Jasmin
9
Deseo comunicar que hice un gran avance en la comprensión de javascript. una especie de sinapsis cerrada. Ahora todo está claro. He visto cosas. Gané un nivel en programador javascript.
Stefano Borini

Respuestas:

68

Actualización Mi respuesta original a continuación fue escrita hace 6 años en un estilo acorde con los tiempos y mi comprensión. En respuesta a alguna conversación en los comentarios, un enfoque más moderno para esto es el siguiente:

(function() {
    if ( typeof Object.id == "undefined" ) {
        var id = 0;

        Object.id = function(o) {
            if ( typeof o.__uniqueid == "undefined" ) {
                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`

Si tiene requisitos de navegador arcaicos, verifique aquí la compatibilidad del navegador para Object.defineProperty.

La respuesta original se mantiene a continuación (en lugar de solo en el historial de cambios) porque creo que la comparación es valiosa.


Puedes darle una vuelta a lo siguiente. Esto también le da la opción de establecer explícitamente el ID de un objeto en su constructor o en otro lugar.

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

Tenga cuidado de asegurarse de que cualquier miembro que utilice para almacenar internamente la ID única no choque con otro nombre de miembro creado automáticamente.

Justin Johnson
fuente
1
@Justin Agregar propiedades a Object.prototype es problemático en ECMAScript 3 porque estas propiedades se pueden enumerar en todos los objetos. Entonces, si define Object.prototype.a entonces "a" será visible cuando lo haga para (prop en {}) alert (prop); Por lo tanto, debe hacer un compromiso entre aumentar Object.prototype y poder iterar a través de objetos similares a registros utilizando un bucle for..in. Este es un problema grave para las bibliotecas
Alex Jasmin
29
No hay compromiso. Durante mucho tiempo se ha considerado una mejor práctica para usar siempre object.hasOwnProperty(member)cuando se usa un for..inbucle. Esta es una práctica bien documentada y aplicada por jslint
Justin Johnson
3
Ninguno de los dos recomendaría hacer esto también, al menos no para todos los objetos, puede hacer lo mismo solo con los objetos que maneja. Desafortunadamente, la mayoría de las veces tenemos que usar bibliotecas javascript externas, y desafortunadamente no todas están bien programadas, así que evite esto a menos que tenga el control total de todas las bibliotecas incluidas en su página web, o al menos sepa que se manejan bien. .
inútil
1
@JustinJohnson: Hay un compromiso en ES5: defineProperty(…, {enumerable:false}). Y el uidmétodo en sí debería estar en el Objectespacio de nombres de todos modos
Bergi
@JustinJohnson si digamos que la identificación del objeto es, 4¿cómo asignarías obj con la identificación 4 a una variable para que puedas hacer cosas con ella ... como acceder a sus propiedades?
Sir
50

En lo que respecta a mi observación, cualquier respuesta publicada aquí puede tener efectos secundarios inesperados.

En un entorno compatible con ES2015, puede evitar cualquier efecto secundario utilizando 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
fuente
1
¿Por qué no return currentId?
Gust van de Wal
¿Por qué a WeakMapdiferencia de Map? Esa función se bloqueará si le proporciona una cadena o un número.
Nate Symer
35

Los navegadores más recientes proporcionan un método más limpio para extender Object.prototype. Este código hará que la propiedad esté oculta de la enumeración de propiedades (para p en o)

Para los navegadores que implementan defineProperty , puede implementar la propiedad uniqueId como esta:

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

Para obtener más información, consulte https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty

Aleksandar Totic
fuente
2
Los "navegadores más recientes" aparentemente no incluyen Firefox 3.6. (Sí, me excluiré de la carrera de actualización en las versiones más nuevas de Firefox, y estoy seguro de que no soy el único. Además, FF3.6 solo tiene 1 año).
Bart
7
El niño de 1 año no es "solo" en este deporte. La web evoluciona dinámicamente, eso es bueno. Los navegadores modernos tienen actualizadores automáticos precisamente con el propósito de permitir eso.
Kos
1
Unos años más tarde, esto parece funcionar mejor para mí que la respuesta aceptada.
Fitter Man
11

En realidad, no es necesario modificar el objectprototipo y agregar una función allí. Lo siguiente debería funcionar bien para su propósito.

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
fuente
4
nocase, snake_case y camelCase se han abierto paso los tres en un fragmento de código de 6 líneas. No ves eso todos los días
Gust van de Wal
esto parece un mecanismo mucho más convincente que los objecttrucos. solo necesita ejecutarlo cuando desee que el objeto del OP coincida y permanece fuera del camino el resto del tiempo.
JL Peyret
6

Para los navegadores que implementan el Object.defineProperty()método, el siguiente código genera y devuelve una función que puede vincular a cualquier objeto que posea.

Este enfoque tiene la ventaja de no extenderse Object.prototype.

El código funciona comprobando si el objeto dado tiene una __objectID__propiedad, y definiéndolo como una propiedad oculta (no enumerable) de solo lectura si no es así.

Por lo tanto, es seguro contra cualquier intento de cambiar o redefinir la obj.__objectID__propiedad de solo lectura después de que se ha definido, y arroja consistentemente un error agradable en lugar de fallar silenciosamente.

Finalmente, en el caso bastante extremo en el que algún otro código ya se hubiera definido __objectID__en un objeto dado, este valor simplemente se devolvería.

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
fuente
4

El código jQuery usa su propio data()método como tal id.

var id = $.data(object);

En el backstage, el método datacrea un campo muy especial en el objectllamado "jQuery" + now()put there next id de una secuencia de identificadores únicos como

id = elem[ expando ] = ++uuid;

Te sugiero que uses el mismo método, ya que John Resig obviamente sabe todo lo que hay sobre JavaScript y su método se basa en todo ese conocimiento.

vava
fuente
5
El datamétodo de jQuery tiene fallas. Consulte, por ejemplo, stackoverflow.com/questions/1915341/… . Además, John Resig de ninguna manera sabe todo lo que hay que saber sobre JavaScript, y creer que lo sabe no le ayudará como desarrollador de JavaScript.
Tim Down
1
@Tim, tiene tantos defectos en términos de obtener una identificación única como cualquier otro método presentado aquí, ya que hace aproximadamente lo mismo bajo el capó. Y sí, creo que John Resig sabe mucho más que yo y debería aprender de sus decisiones incluso si no es Douglas Crockford.
vava
AFAIK $ .data no funciona en objetos JavaScript sino solo en elementos DOM.
mb21
4

Versión mecanografiada de @justin answer, compatible con ES6, que usa símbolos para evitar cualquier colisión de teclas y se agrega al Object.id global para mayor comodidad. Simplemente copie y pegue el código a continuación o colóquelo en un archivo ObjecId.ts que importará.

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

Ejemplo de uso:

console.log(Object.id(myObject));
Flavien Volken
fuente
1

He usado un código como este, que hará que los Objetos se encadenen con cadenas únicas:

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__ + ']';
};

los __proto__bits son para evitar que el __id__captador aparezca en el objeto. esto solo se ha probado en Firefox.

Eric Strom
fuente
Solo tenga en cuenta que __defineGetter__no es estándar.
Tomáš Zato - Reincorporación a Monica
1

A pesar del consejo de no modificar Object.prototype, esto aún puede ser realmente útil para probar, dentro de un alcance limitado. El autor de la respuesta aceptada la cambió, pero aún está configurando Object.id, lo que no tiene sentido para mí. Aquí hay un fragmento que hace el trabajo:

// 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
fuente
1

Enfrenté el mismo problema y aquí está la solución que implementé con ES6

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

cat = new Animal("Catty");


console.log(cat.id) // 1 
Jaime Rios
fuente
1

Con el fin de comparar dos objetos, la forma más sencilla de hacerlo sería agregar una propiedad única a uno de los objetos en el momento en que necesite comparar los objetos, verificar si la propiedad existe en el otro y luego eliminarlo nuevamente. Esto ahorra prototipos primordiales.

function isSameObject(objectA, objectB) {
   unique_ref = "unique_id_" + performance.now();
   objectA[unique_ref] = true;
   isSame = objectB.hasOwnProperty(unique_ref);
   delete objectA[unique_ref];
   return isSame;
}

object1 = {something:true};
object2 = {something:true};
object3 = object1;

console.log(isSameObject(object1, object2)); //false
console.log(isSameObject(object1, object3)); //true
adevart
fuente