identificador de objeto único em javascript

119

Preciso fazer alguns experimentos e conhecer algum tipo de identificador único para objetos em javascript, para ver se são iguais. Não quero usar operadores de igualdade, preciso de algo como a função id () em python.

Será que algo assim existe ?

Stefano Borini
fonte
2
Estou um pouco curioso, por que você quer evitar os operadores de igualdade?
CMS
22
porque quero algo simples e quero ver um número, algo claro. Essa linguagem faz a gatinha chorar, já estou lutando o suficiente.
Stefano Borini
4
O operador de igualdade estrita (===) fará o que você pedir em objetos (se você estiver comparando números / strings / etc, não é a mesma coisa), e é mais simples do que construir um ID secreto exclusivo em cada objeto.
Ben Zotto
4
@CMS @Ben Ter IDs exclusivos pode ser útil para depurar ou implementar coisas como um IdentitySet.
Alex Jasmin
9
Desejo comunicar que fiz um avanço no entendimento do javascript. meio que encerrou uma sinapse. Agora está tudo claro. Eu vi coisas. Ganhei um nível de programador javascript.
Stefano Borini

Respostas:

68

Atualização Minha resposta original abaixo foi escrita há 6 anos em um estilo adequado à época e ao meu entendimento. Em resposta a alguma conversa nos comentários, uma abordagem mais moderna para isso é a seguinte:

(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`

Se você tiver requisitos de navegador arcaicos, verifique aqui a compatibilidade do navegador para Object.defineProperty.

A resposta original é mantida abaixo (em vez de apenas no histórico de alterações) porque acho que a comparação é valiosa.


Você pode experimentar o seguinte. Isso também oferece a opção de definir explicitamente o ID de um objeto em seu construtor ou em outro 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());

Tome cuidado para certificar-se de que qualquer membro que você usar para armazenar internamente o ID exclusivo não colida com outro nome de membro criado automaticamente.

Justin Johnson
fonte
1
@Justin Adicionar propriedades a Object.prototype é problemático no ECMAScript 3 porque essas propriedades são enumeráveis ​​em todos os objetos. Portanto, se você definir Object.prototype.a , "a" ficará visível quando você fizer para (prop em {}) alert (prop); Portanto, você tem que fazer um meio-termo entre aumentar Object.prototype e ser capaz de iterar por meio de registros como objetos usando um loop for..in. Este é um problema sério para as bibliotecas
Alex Jasmin
29
Não há compromisso. Há muito tempo é considerada uma prática recomendada sempre usar object.hasOwnProperty(member)ao usar um for..inloop. Esta é uma prática bem documentada e aplicada por jslint
Justin Johnson
3
nenhum dos eu recomendaria fazer isso também, pelo menos não para todos os objetos, você pode fazer o mesmo apenas com os objetos que manipula. infelizmente, na maioria das vezes temos que usar bibliotecas javascript externas e, infelizmente, nem todas elas são bem programadas, então evite isso a menos que você tenha controle total de todas as bibliotecas incluídas em sua página da web, ou pelo menos você saiba que elas funcionam bem .
inútil,
1
@JustinJohnson: Há um compromisso em ES5: defineProperty(…, {enumerable:false}). E o uidmétodo em si deve estar no Objectnamespace de qualquer maneira
Bergi
@JustinJohnson se digamos que o id do objeto era 4como você atribuiria obj com o id 4 a uma variável para que você possa fazer coisas com ele ... como acessar suas propriedades?
Senhor
50

Até onde vai minha observação, qualquer resposta postada aqui pode ter efeitos colaterais inesperados.

Em um ambiente compatível com ES2015, você pode evitar quaisquer efeitos colaterais usando o 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
fonte
1
Porque não return currentId?
Gust van de Wal
Por WeakMapque em oposição a Map? Essa função irá falhar se você fornecer uma string ou número.
Nate Symer de
35

Os navegadores mais recentes fornecem um método mais limpo para estender Object.prototype. Este código tornará a propriedade oculta da enumeração de propriedade (para p em o)

Para os navegadores que implementam defineProperty , você pode implementar a propriedade uniqueId como este:

(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 obter detalhes, consulte https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty

Aleksandar Totic
fonte
2
Aparentemente, os "navegadores mais recentes" não incluem o Firefox 3.6. (Sim, estou optando por sair da corrida de atualização nas versões mais recentes do Firefox e tenho certeza de que não sou o único. Além disso, o FF3.6 tem apenas 1 ano de idade.)
bart
7
Criança de 1 ano não é "apenas" neste esporte. A web evolui dinamicamente - isso é bom. Os navegadores modernos têm atualizadores automáticos precisamente para permitir isso.
Kos
1
Alguns anos depois, isso parece estar funcionando melhor para mim do que a resposta aceita.
Fitter Man
11

Na verdade, você não precisa modificar o objectprotótipo e adicionar uma função lá. O seguinte deve funcionar bem para o seu 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
fonte
4
nocase, snake_case e camelCase abriram caminho para um trecho de código de 6 linhas. Você não vê isso todos os dias
Gust van de Wal
este parece ser um mecanismo muito mais convincente do que objecthacks. você só precisa executá-lo quando quiser que o objeto do OP corresponda e ele não atrapalhe o resto do tempo.
JL Peyret
6

Para navegadores que implementam o Object.defineProperty()método, o código a seguir gera e retorna uma função que você pode vincular a qualquer objeto de sua propriedade.

Essa abordagem tem a vantagem de não se estender Object.prototype.

O código funciona verificando se o objeto fornecido tem uma __objectID__propriedade e definindo-o como uma propriedade somente leitura oculta (não enumerável), caso não tenha.

Portanto, é seguro contra qualquer tentativa de alterar ou redefinir a obj.__objectID__propriedade somente leitura após ela ter sido definida e gera consistentemente um bom erro em vez de falhar silenciosamente.

Finalmente, no caso extremo em que algum outro código já teria definido __objectID__em um determinado objeto, esse valor seria simplesmente retornado.

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

O código jQuery usa seu próprio data()método como id.

var id = $.data(object);

No backstage, o método datacria um campo muito especial objectchamado, "jQuery" + now()coloque o próximo id de um fluxo de ids únicos como

id = elem[ expando ] = ++uuid;

Eu sugiro que você use o mesmo método que John Resig, obviamente sabe tudo sobre JavaScript e seu método é baseado em todo esse conhecimento.

vava
fonte
5
O datamétodo jQuery tem falhas. Veja, por exemplo, stackoverflow.com/questions/1915341/… . Além disso, John Resig de forma alguma sabe tudo o que há para saber sobre JavaScript, e acreditar que ele sabe não o ajudará como desenvolvedor de JavaScript.
Tim Down
1
@Tim, ele tem tantas falhas em termos de obtenção de id único quanto qualquer outro método apresentado aqui, pois faz praticamente o mesmo por baixo do capô. E sim, acredito que John Resig sabe muito mais do que eu e eu deveria aprender com suas decisões, mesmo que ele não seja Douglas Crockford.
vava
AFAIK $ .data não funciona em objetos JavaScript, mas apenas em elementos DOM.
mb21
4

Versão datilografada de @justin answer, compatível com ES6, usando símbolos para evitar qualquer colisão de chaves e adicionado ao Object.id global por conveniência. Basta copiar e colar o código abaixo ou colocá-lo em um arquivo ObjecId.ts que você 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];
    };
}

Exemplo de uso:

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

Usei um código como este, o que fará com que os objetos sequenciem com strings exclusivas:

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

os __proto__bits são para evitar que o __id__getter apareça no objeto. isso só foi testado no firefox.

Eric Strom
fonte
Lembre-se de que isso __defineGetter__não é padrão.
Tomáš Zato - Reintegrar Monica em
1

Apesar do conselho de não modificar Object.prototype, isso ainda pode ser realmente útil para teste, dentro de um escopo limitado. O autor da resposta aceita mudou, mas ainda está definindo Object.id, o que não faz sentido para mim. Aqui está um snippet que faz o trabalho:

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

Eu enfrentei o mesmo problema e aqui está a solução que implementei com 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
fonte
1

Para efeito de comparação de dois objetos, a maneira mais simples de fazer isso seria adicionar uma propriedade única a um dos objetos no momento em que você precisa comparar os objetos, verificar se a propriedade existe no outro e removê-la novamente. Isso salva protótipos sobrescritos.

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
fonte