eindeutige Objektkennung in Javascript

119

Ich muss einige Experimente durchführen und eine eindeutige Kennung für Objekte in Javascript kennen, damit ich sehen kann, ob sie identisch sind. Ich möchte keine Gleichheitsoperatoren verwenden, ich brauche so etwas wie die Funktion id () in Python.

Gibt es so etwas?

Stefano Borini
quelle
2
Ich bin ein bisschen neugierig, warum Sie die Gleichheitsoperatoren vermeiden wollen?
CMS
22
weil ich etwas Einfaches will und eine Zahl sehen will, etwas Klares. Diese Sprache bringt Kätzchen zum Weinen, ich kämpfe schon genug dagegen.
Stefano Borini
4
Der strikte Gleichheitsoperator (===) erledigt das, wonach Sie für Objekte fragen (wenn Sie Zahlen / Zeichenfolgen / usw. vergleichen, ist dies nicht dasselbe) und ist einfacher als das Erstellen einer geheimen eindeutigen ID in jedes Objekt.
Ben Zotto
4
@CMS @Ben Eindeutige IDs können nützlich sein, um Dinge wie ein IdentitySet zu debuggen oder zu implementieren.
Alex Jasmin
9
Ich möchte mitteilen, dass ich einen Durchbruch beim Verständnis von Javascript erzielt habe. Art schloss eine Synapse. Jetzt ist alles klar. Ich habe Dinge gesehen. Ich habe ein Niveau in Javascript-Programmierer erreicht.
Stefano Borini

Antworten:

68

Update Meine ursprüngliche Antwort unten wurde vor 6 Jahren in einem Stil geschrieben, der der Zeit und meinem Verständnis entspricht. Als Reaktion auf einige Gespräche in den Kommentaren lautet ein moderner Ansatz wie folgt:

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

Wenn Sie archaische Browseranforderungen haben, überprüfen Sie hier die Browserkompatibilität für Object.defineProperty.

Die ursprüngliche Antwort wird unten aufbewahrt (anstatt nur in der Änderungshistorie), da ich den Vergleich für wertvoll halte.


Sie können Folgendes ausprobieren. Dies gibt Ihnen auch die Möglichkeit, die ID eines Objekts explizit in seinem Konstruktor oder anderswo festzulegen.

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

Stellen Sie sicher, dass das Mitglied, mit dem Sie die eindeutige ID intern speichern, nicht mit einem anderen automatisch erstellten Mitgliedsnamen kollidiert.

Justin Johnson
quelle
1
@Justin Das Hinzufügen von Eigenschaften zu Object.prototype ist in ECMAScript 3 problematisch, da diese Eigenschaften für alle Objekte aufzählbar sind. Wenn Sie also Object.prototype.a definieren, wird "a" angezeigt, wenn Sie für (prop in {}) alert (prop); Sie müssen also einen Kompromiss zwischen der Erweiterung von Object.prototype und der Iteration durch aufzeichnungsähnliche Objekte mithilfe einer for..in-Schleife eingehen. Dies ist ein ernstes Problem für Bibliotheken
Alex Jasmin
29
Es gibt keinen Kompromiss. Es wird seit langem als bewährte Methode angesehen, immer object.hasOwnProperty(member)eine for..inSchleife zu verwenden. Dies ist eine gut dokumentierte Praxis, die von jslint durchgesetzt wird
Justin Johnson
3
Keiner von beiden würde empfehlen, dies auch zu tun, zumindest nicht für jedes Objekt. Sie können dasselbe nur mit den Objekten tun, die Sie bearbeiten. Leider müssen wir die meiste Zeit externe Javascript-Bibliotheken verwenden, und leider sind nicht alle gut programmiert. Vermeiden Sie dies, es sei denn, Sie haben die vollständige Kontrolle über alle auf Ihrer Webseite enthaltenen Bibliotheken oder wissen zumindest, dass sie gut gehandhabt werden .
nutzlos
1
@JustinJohnson: In ES5 gibt es einen Kompromiss : defineProperty(…, {enumerable:false}). Und die uidMethode selbst sollte Objectsowieso im Namespace sein
Bergi
@JustinJohnson Wenn wir sagen, die Objekt-ID war, 4wie würden Sie dann einer Variablen obj mit der ID 4 zuweisen, damit Sie damit etwas anfangen können ... wie auf seine Eigenschaften zugreifen?
Sir
50

Nach meiner Beobachtung kann jede hier veröffentlichte Antwort unerwartete Nebenwirkungen haben.

In einer ES2015-kompatiblen Umgebung können Sie mithilfe von WeakMap Nebenwirkungen vermeiden .

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
quelle
1
Warum nicht return currentId?
Gust van de Wal
Warum WeakMapim Gegensatz zu Map? Diese Funktion stürzt ab, wenn Sie eine Zeichenfolge oder Nummer eingeben.
Nate Symer
35

Die neuesten Browser bieten eine übersichtlichere Methode zum Erweitern von Object.prototype. Dieser Code macht die Eigenschaft vor der Eigenschaftsaufzählung verborgen (für p in o)

Für die Browser, die defineProperty implementieren , können Sie die uniqueId-Eigenschaft wie folgt implementieren:

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

Weitere Informationen finden Sie unter https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty

Aleksandar Totic
quelle
2
"Neueste Browser" enthalten anscheinend nicht Firefox 3.6. (Ja, ich habe mich in den neueren Versionen von Firefox vom Upgrade-Rennen abgemeldet, und ich bin sicher, dass ich nicht der einzige bin. Außerdem ist FF3.6 erst 1 Jahr alt.)
Bart
7
1 Jahr alt ist nicht "nur" in diesem Sport. Das Web entwickelt sich dynamisch - das ist gut so. Moderne Browser verfügen über automatische Updater, um dies zu ermöglichen.
Kos
1
Ein paar Jahre später scheint dies für mich besser zu funktionieren als die akzeptierte Antwort.
Fitter Man
11

Eigentlich müssen Sie den objectPrototyp nicht ändern und dort eine Funktion hinzufügen. Das Folgende sollte für Ihren Zweck gut funktionieren.

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
quelle
3
nocase, snake_case und camelCase haben alle drei ihren Weg in ein 6-zeiliges Code-Snippet gefunden. Das sieht man nicht jeden Tag
Gust van de Wal
Dies scheint ein viel überzeugenderer Mechanismus zu sein als objectHacks. Sie müssen es nur ausführen, wenn Sie möchten, dass das Objekt des OP übereinstimmt, und es bleibt den Rest der Zeit aus dem Weg.
JL Peyret
6

Für Browser, die die Object.defineProperty()Methode implementieren , generiert der folgende Code eine Funktion, die Sie an jedes Objekt binden können, das Sie besitzen.

Dieser Ansatz hat den Vorteil, dass er nicht erweitert wird Object.prototype.

Der Code überprüft, ob das angegebene Objekt eine __objectID__Eigenschaft hat, und definiert es als versteckte (nicht aufzählbare) schreibgeschützte Eigenschaft, wenn nicht.

Es ist also sicher gegen jeden Versuch, die schreibgeschützte obj.__objectID__Eigenschaft nach ihrer Definition zu ändern oder neu zu definieren, und löst durchweg einen netten Fehler aus, anstatt stillschweigend fehlzuschlagen.

Im extremen Fall, in dem bereits __objectID__ein anderer Code für ein bestimmtes Objekt definiert worden wäre, würde dieser Wert einfach zurückgegeben.

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

jQuery-Code verwendet eine eigene data()Methode als solche ID.

var id = $.data(object);

Bei der Backstage-Methode datawird ein ganz besonderes Feld in der so objectgenannten "jQuery" + now()put there next-ID eines Streams eindeutiger IDs wie erstellt

id = elem[ expando ] = ++uuid;

Ich würde vorschlagen, dass Sie dieselbe Methode verwenden, mit der John Resig offensichtlich alles über JavaScript weiß, und seine Methode basiert auf all diesem Wissen.

vava
quelle
5
Die dataMethode von jQuery weist Fehler auf. Siehe zum Beispiel stackoverflow.com/questions/1915341/… . Außerdem weiß John Resig keineswegs alles, was es über JavaScript zu wissen gibt, und zu glauben, dass er Ihnen als JavaScript-Entwickler nicht hilft.
Tim Down
1
@ Tim, es hat genauso viele Fehler in Bezug auf das Erhalten einer eindeutigen ID wie jede andere hier vorgestellte Methode, da es unter der Haube ungefähr dasselbe tut. Und ja, ich glaube, John Resig weiß viel mehr als ich und ich sollte aus seinen Entscheidungen lernen, auch wenn er nicht Douglas Crockford ist.
Vava
AFAIK $ .data funktioniert nicht mit JavaScript-Objekten, sondern nur mit DOM-Elementen.
mb21
4

Typoskript-Version von @justin answer, ES6-kompatibel, mit Symbolen, um Schlüsselkollisionen zu verhindern, und der Einfachheit halber in die globale Object.id eingefügt. Kopieren Sie einfach den folgenden Code, fügen Sie ihn ein oder fügen Sie ihn in eine ObjecId.ts-Datei ein, die Sie importieren möchten.

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

Anwendungsbeispiel:

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

Ich habe Code wie diesen verwendet, der dazu führt, dass Objekte mit eindeutigen Zeichenfolgen stringifiziert werden:

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

Die __proto__Bits sollen verhindern, dass der __id__Getter im Objekt auftaucht. Dies wurde nur in Firefox getestet.

Eric Strom
quelle
Denken Sie daran, dass dies __defineGetter__nicht dem Standard entspricht.
Tomáš Zato - Wiedereinsetzung Monica
1

Ungeachtet der Empfehlung, Object.prototype nicht zu ändern, kann dies in einem begrenzten Umfang dennoch zum Testen sehr nützlich sein. Der Autor der akzeptierten Antwort hat sie geändert, stellt sie aber immer noch ein Object.id, was für mich keinen Sinn ergibt. Hier ist ein Ausschnitt, der den Job macht:

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

Ich hatte das gleiche Problem und hier ist die Lösung, die ich mit ES6 implementiert habe

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
quelle
1

Um zwei Objekte zu vergleichen, besteht die einfachste Möglichkeit darin, einem der Objekte zu dem Zeitpunkt, zu dem Sie die Objekte vergleichen müssen, eine eindeutige Eigenschaft hinzuzufügen, zu überprüfen, ob die Eigenschaft in dem anderen vorhanden ist, und sie dann erneut zu entfernen. Dies spart übergeordnete Prototypen.

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
quelle