identificatore di oggetto univoco in javascript


120

Devo fare qualche esperimento e ho bisogno di conoscere una sorta di identificatore univoco per oggetti in javascript, così posso vedere se sono gli stessi. Non voglio usare operatori di uguaglianza, ho bisogno di qualcosa come la funzione id () in Python.

Esiste qualcosa di simile?


2
Sono un po 'curioso, perché vuoi evitare gli operatori di uguaglianza?
CMS

22
perché voglio qualcosa di semplice e voglio vedere un numero, qualcosa di chiaro. Questa lingua fa piangere il gattino, lo sto già combattendo abbastanza.
Stefano Borini

4
L'operatore di uguaglianza rigorosa (===) farà ciò che chiedi sugli oggetti (se stai confrontando numeri / stringhe / ecc. Non è la stessa cosa) ed è più semplice che costruire un ID univoco segreto in ogni oggetto.
Ben Zotto

4
@CMS @Ben Avere ID univoci può essere utile per il debug o l'implementazione di cose come un IdentitySet.
Alex Jasmin

9
Desidero comunicare che ho fatto una svolta nella comprensione di javascript. una specie di sinapsi chiusa. Adesso è tutto chiaro. Ho visto delle cose. Ho acquisito un livello di programmatore javascript.
Stefano Borini

Risposte:


68

Aggiorna La mia risposta originale di seguito è stata scritta 6 anni fa in uno stile che si addice ai tempi e alla mia comprensione. In risposta ad alcune conversazioni nei commenti, un approccio più moderno a questo è il seguente:

(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 hai requisiti del browser arcaici, controlla qui per la compatibilità del browser per Object.defineProperty.

La risposta originale è mantenuta di seguito (anziché solo nella cronologia delle modifiche) perché penso che il confronto sia prezioso.


Puoi dare un giro al seguente. Questo ti dà anche la possibilità di impostare esplicitamente l'ID di un oggetto nel suo costruttore o altrove.

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

Assicurati che qualsiasi membro che utilizzi per memorizzare internamente l'ID univoco non entri in conflitto con un altro nome membro creato automaticamente.


1
@Justin L'aggiunta di proprietà a Object.prototype è problematica in ECMAScript 3 perché queste proprietà sono enumerabili su tutti gli oggetti. Quindi se definisci Object.prototype.a allora "a" sarà visibile quando esegui per (prop in {}) alert (prop); Quindi devi fare un compromesso tra l'aumento di Object.prototype e la possibilità di iterare attraverso oggetti simili a record usando un ciclo for..in. Questo è un problema serio per le biblioteche
Alex Jasmin

29
Non ci sono compromessi. È stata a lungo considerata una best practice da utilizzare sempre object.hasOwnProperty(member)quando si utilizza un for..inloop. Questa è una pratica ben documentata e applicata da jslint
Justin Johnson

3
nessuno dei due consiglierei di farlo anche, almeno non per ogni oggetto, puoi fare lo stesso solo con gli oggetti che maneggi. sfortunatamente la maggior parte delle volte dobbiamo usare librerie javascript esterne, e sfortunatamente non tutte sono ben programmate quindi evitalo a meno che tu non abbia il controllo totale di tutte le librerie incluse nella tua pagina web, o almeno sai che sono gestite bene .
inutile

1
@JustinJohnson: V'è un compromesso in ES5: defineProperty(…, {enumerable:false}). E il uidmetodo stesso dovrebbe essere Objectcomunque nello spazio dei nomi
Bergi

@JustinJohnson se diciamo che l'id oggetto fosse, 4come assegneresti quindi obj con l'id 4 a una variabile in modo da poter fare cose con essa ... come accedere alle sue proprietà?
Sir

50

Per quanto riguarda la mia osservazione, qualsiasi risposta pubblicata qui può avere effetti collaterali inaspettati.

Nell'ambiente compatibile con ES2015, puoi evitare qualsiasi effetto collaterale utilizzando 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
Perché no return currentId?
Gust van de Wal

Perché WeakMapal contrario di Map? Quella funzione andrà in crash se le fornisci una stringa o un numero.
Nate Symer

35

I browser più recenti forniscono un metodo più pulito per estendere Object.prototype. Questo codice renderà la proprietà nascosta dall'enumerazione delle proprietà (per p in o)

Per i browser che implementano defineProperty , puoi implementare la proprietà uniqueId come questa:

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

Per i dettagli, vedere https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty


2
"Gli ultimi browser" apparentemente non includono Firefox 3.6. (Sì, sto escludendo la corsa all'upgrade nelle versioni più recenti di Firefox e sono sicuro di non essere l'unico. Inoltre, FF3.6 ha solo 1 anno.)
bart

7
Il bambino di 1 anno non è "solo" in questo sport. Il Web si evolve in modo dinamico: è una buona cosa. I browser moderni dispongono di aggiornamenti automatici proprio allo scopo di consentirlo.
Kos

1
Alcuni anni dopo, questo sembra funzionare meglio per me rispetto alla risposta accettata.
Fitter Man

11

In realtà, non è necessario modificare il objectprototipo e aggiungere una funzione lì. Quanto segue dovrebbe funzionare bene per il tuo scopo.

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 sono stati inseriti tutti e tre in uno snippet di codice di 6 righe. Non lo vedi tutti i giorni
Gust van de Wal

questo sembra un meccanismo molto più convincente degli objecthack. è necessario eseguirlo solo quando si desidera la corrispondenza degli oggetti dell'OP e rimane fuori mano per il resto del tempo.
JL Peyret,

6

Per i browser che implementano il Object.defineProperty()metodo, il codice seguente genera e restituisce una funzione che puoi associare a qualsiasi oggetto di tua proprietà.

Questo approccio ha il vantaggio di non estendersi Object.prototype.

Il codice funziona controllando se l'oggetto dato ha una __objectID__proprietà e definendolo come una proprietà di sola lettura nascosta (non enumerabile) in caso contrario.

Quindi è al sicuro da qualsiasi tentativo di modificare o ridefinire la obj.__objectID__proprietà di sola lettura dopo che è stata definita e genera costantemente un bell'errore invece di fallire silenziosamente.

Infine, nel caso abbastanza estremo in cui qualche altro codice sarebbe già stato definito __objectID__su un dato oggetto, questo valore verrebbe semplicemente restituito.

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

Il codice jQuery utilizza il proprio data()metodo come tale ID.

var id = $.data(object);

Nel backstage il metodo datacrea un campo molto speciale objectchiamato "jQuery" + now()put there next id di un flusso di ID univoci come

id = elem[ expando ] = ++uuid;

Suggerirei di utilizzare lo stesso metodo di John Resig, ovviamente, sa tutto su JavaScript e il suo metodo si basa su tutta questa conoscenza.


5
Il datametodo di jQuery ha dei difetti. Vedi ad esempio stackoverflow.com/questions/1915341/… . Inoltre, John Resig non sa assolutamente tutto quello che c'è da sapere su JavaScript e credere che non ti aiuterà come sviluppatore JavaScript.
Tim Down

1
@Tim, ha tanti difetti in termini di ottenere un ID univoco come qualsiasi altro metodo presentato qui poiché fa più o meno lo stesso sotto il cofano. E sì, credo che John Resig sappia molto più di me e dovrei imparare dalle sue decisioni anche se non è Douglas Crockford.
vava

AFAIK $ .data non funziona sugli oggetti JavaScript ma solo sugli elementi DOM.
mb21

4

Versione dattiloscritta di @justin answer, compatibile con ES6, che utilizza i simboli per evitare qualsiasi collisione di tasti e aggiunta nel file Object.id globale per comodità. Copia e incolla il codice qui sotto o inseriscilo in un file ObjecId.ts che importerai.

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

Esempio di utilizzo:

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

1

Ho usato codice come questo, che farà sì che gli oggetti si stringano con stringhe univoche:

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

i __proto__bit servono per impedire al __id__getter di apparire nell'oggetto. questo è stato testato solo in Firefox.


Tieni presente che __defineGetter__non è standard.
Tomáš Zato - Ripristina Monica il

1

Nonostante il consiglio di non modificare Object.prototype, questo può comunque essere davvero utile per i test, in un ambito limitato. L'autore della risposta accettata l'ha cambiato, ma sta ancora impostando Object.id, il che non ha senso per me. Ecco uno snippet che fa il lavoro:

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

Ho affrontato lo stesso problema ed ecco la soluzione che ho implementato 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 

1

Allo scopo di confrontare due oggetti, il modo più semplice per farlo sarebbe aggiungere una proprietà univoca a uno degli oggetti nel momento in cui è necessario confrontare gli oggetti, controllare se la proprietà esiste nell'altro e quindi rimuoverla di nuovo. Ciò consente di risparmiare prototipi di override.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.