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 ?


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.


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

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


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

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

         }
    };

})();

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.


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

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.


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

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 

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.