unikalny identyfikator obiektu w javascript

120

Muszę przeprowadzić jakiś eksperyment i muszę znać jakiś unikalny identyfikator obiektów w javascript, żeby sprawdzić, czy są takie same. Nie chcę używać operatorów równości, potrzebuję czegoś takiego jak funkcja id () w Pythonie.

Czy coś takiego istnieje?

Stefano Borini
źródło
2
Jestem trochę ciekawy, dlaczego chcesz uniknąć operatorów równości?
CMS
22
ponieważ chcę czegoś prostego i chcę zobaczyć liczbę, coś jasnego. Ten język sprawia, że ​​kociak płacze, już wystarczająco walczę.
Stefano Borini
4
Operator ścisłej równości (===) zrobi to, o co prosisz na obiektach (jeśli porównujesz liczby / ciągi znaków / itp., To nie to samo) i jest prostszy niż budowanie tajnego unikalnego identyfikatora w każdym obiekcie.
Ben Zotto
4
@CMS @Ben Posiadanie unikalnych identyfikatorów może być przydatne do debugowania lub implementowania rzeczy takich jak IdentitySet.
Alex Jasmin
9
Pragnę zakomunikować, że dokonałem przełomu w zrozumieniu javascript. jakby zamknęła synapsę. Teraz wszystko jest jasne. Widziałem różne rzeczy. Zdobyłem poziom programisty javascript.
Stefano Borini

Odpowiedzi:

68

Zaktualizuj Moja oryginalna odpowiedź poniżej została napisana 6 lat temu w stylu stosownym do czasów i mojego zrozumienia. W odpowiedzi na pewną rozmowę w komentarzach, bardziej nowoczesne podejście do tego jest następujące:

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

Jeśli masz archaiczne wymagania dotyczące przeglądarki, sprawdź tutaj zgodność przeglądarki Object.defineProperty.

Oryginalna odpowiedź jest przechowywana poniżej (zamiast tylko w historii zmian), ponieważ uważam, że porównanie jest wartościowe.


Możesz dać następujący obrót. Daje to również opcję jawnego ustawienia identyfikatora obiektu w jego konstruktorze lub w innym miejscu.

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

Uważaj, aby upewnić się, że członek, którego używasz do wewnętrznego przechowywania unikalnego identyfikatora, nie koliduje z inną automatycznie utworzoną nazwą członka.

Justin Johnson
źródło
1
@Justin Dodawanie właściwości do Object.prototype jest problematyczne w ECMAScript 3, ponieważ te właściwości są wyliczalne dla wszystkich obiektów. Więc jeśli zdefiniujesz Object.prototype.a, wtedy "a" będzie widoczne, gdy zrobisz for (prop in {}) alert (prop); Musisz więc pójść na kompromis między rozszerzaniem Object.prototype a możliwością iteracji po rekordach, takich jak obiekty, przy użyciu pętli for..in. To poważny problem dla bibliotek
Alex Jasmin
29
Nie ma kompromisów. Od dawna uważano, że najlepsza praktyka jest zawsze używana object.hasOwnProperty(member)podczas korzystania z for..inpętli. Jest to dobrze udokumentowana praktyka, którą egzekwuje jslint
Justin Johnson
3
żaden z nich nie polecałbym robić tego również, przynajmniej nie dla każdego obiektu, możesz zrobić to samo tylko z obiektami, które obsługujesz. niestety przez większość czasu musimy korzystać z zewnętrznych bibliotek javascript i niestety nie każda z nich jest dobrze zaprogramowana, więc unikaj tego, chyba że masz całkowitą kontrolę nad wszystkimi bibliotekami zawartymi na swojej stronie internetowej lub przynajmniej wiesz, że dobrze sobie radziły .
bezużyteczne
1
@JustinJohnson: Jest kompromis w ES5: defineProperty(…, {enumerable:false}). I tak uidsama metoda powinna znajdować się w Objectprzestrzeni nazw
Bergi,
@JustinJohnson, jeśli powiedzmy, że identyfikator obiektu był taki, 4jak przypiszesz obiekt o identyfikatorze 4 do zmiennej, abyś mógł z nią robić rzeczy ... na przykład uzyskać dostęp do jej właściwości?
Sir
50

Jeśli chodzi o moje obserwacje, każda zamieszczona tutaj odpowiedź może mieć nieoczekiwane skutki uboczne.

W środowisku zgodnym z ES2015 możesz uniknąć skutków ubocznych, używając 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
źródło
1
Dlaczego nie return currentId?
Gust van de Wal
Dlaczego WeakMapw przeciwieństwie do Map? Ta funkcja ulegnie awarii, jeśli podasz jej ciąg lub liczbę.
Nate Symer
35

Najnowsze przeglądarki zapewniają czystszą metodę rozszerzania Object.prototype. Ten kod spowoduje, że właściwość będzie ukryta przed wyliczeniem właściwości (dla p in o)

W przypadku przeglądarek, które implementują defineProperty , możesz zaimplementować właściwość uniqueId w następujący sposób:

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

Aby uzyskać szczegółowe informacje, zobacz https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty

Aleksandar Totic
źródło
2
„Najnowsze przeglądarki” najwyraźniej nie obejmują Firefoksa 3.6. (Tak, rezygnuję z wyścigu aktualizacji w nowszych wersjach Firefoksa i jestem pewien, że nie jestem jedyny. Poza tym FF3.6 ma tylko 1 rok.)
bart
7
1-latek nie jest „tylko” w tym sporcie. Sieć rozwija się dynamicznie - to dobrze. Nowoczesne przeglądarki mają automatyczne aktualizacje właśnie w celu umożliwienia tego.
Kos
1
Kilka lat później wydaje mi się, że działa to lepiej niż zaakceptowana odpowiedź.
Fitter Man
11

Właściwie nie musisz modyfikować objectprototypu i dodawać tam funkcji. Poniższe działania powinny dobrze działać w Twoim celu.

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
źródło
4
nocase, snake_case i camelCase wszystkie trzy znalazły się w 6-wierszowym fragmencie kodu. Nie widzisz tego codziennie
Gust van de Wal
wydaje się, że jest to o wiele bardziej przekonujący mechanizm niż objecthacki. musisz go uruchamiać tylko wtedy, gdy chcesz dopasować obiekt OP i pozostaje on na uboczu przez resztę czasu.
JL Peyret
6

W przypadku przeglądarek implementujących tę Object.defineProperty()metodę poniższy kod generuje i zwraca funkcję, którą można powiązać z dowolnym posiadanym obiektem.

Takie podejście ma tę zaletę, że nie rozszerza się Object.prototype.

Kod działa, sprawdzając, czy dany obiekt ma __objectID__właściwość, a jeśli tak nie jest , definiując go jako ukrytą (niewliczalną) właściwość tylko do odczytu.

Jest więc bezpieczny przed jakąkolwiek próbą zmiany lub przedefiniowania obj.__objectID__właściwości tylko do odczytu po jej zdefiniowaniu i konsekwentnie generuje ładny błąd zamiast cichego niepowodzenia.

Wreszcie, w dość skrajnym przypadku, gdy jakiś inny kod byłby już zdefiniowany __objectID__na danym obiekcie, ta wartość zostanie po prostu zwrócona.

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
źródło
4

Kod jQuery używa własnej data()metody jako takiego identyfikatora.

var id = $.data(object);

Na zapleczu metoda datatworzy bardzo specjalne pole o objectnazwie "jQuery" + now()put tam next id strumienia unikalnych identyfikatorów, takich jak

id = elem[ expando ] = ++uuid;

Sugerowałbym, abyś użył tej samej metody, ponieważ John Resig oczywiście wie wszystko o JavaScript, a jego metoda opiera się na całej tej wiedzy.

vava
źródło
5
dataMetoda jQuery ma wady. Zobacz na przykład stackoverflow.com/questions/1915341/… . Poza tym John Resig w żadnym wypadku nie wie wszystkiego, co trzeba wiedzieć o JavaScript, a przekonanie, że to nie pomoże, jako programista JavaScript.
Tim Down
1
@Tim, ma tyle wad pod względem uzyskiwania unikalnego identyfikatora, jak każda inna prezentowana tutaj metoda, ponieważ robi mniej więcej to samo pod maską. I tak, uważam, że John Resig wie dużo więcej niż ja i powinienem uczyć się z jego decyzji, nawet jeśli nie jest Douglasem Crockfordem.
vava
AFAIK $ .data nie działa na obiektach JavaScript, ale tylko na elementach DOM.
mb21
4

Wersja @justin answer zgodna z ES6 na maszynie, wykorzystująca symbole, aby zapobiec kolizji kluczy i dodana dla wygody do globalnego Object.id. Po prostu skopiuj, wklej poniższy kod lub umieść go w pliku ObjecId.ts, który zaimportujesz.

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

Przykład użycia:

console.log(Object.id(myObject));
Flavien Volken
źródło
1

Użyłem takiego kodu, który spowoduje, że Objects będzie ciągnąć za pomocą unikalnych ciągów:

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

te __proto__bity są zachować __id__getter od pojawiać się w obiekcie. zostało to przetestowane tylko w przeglądarce Firefox.

Eric Strom
źródło
Pamiętaj tylko, że __defineGetter__jest to niestandardowe.
Tomáš Zato - Przywróć Monikę
1

Pomimo porady, aby nie modyfikować Object.prototype, może to być naprawdę przydatne do testowania w ograniczonym zakresie. Autor zaakceptowanej odpowiedzi zmienił ją, ale nadal ustala Object.id, co nie ma dla mnie sensu. Oto fragment, który spełnia swoje zadanie:

// 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
źródło
1

Napotkałem ten sam problem i oto rozwiązanie, które zaimplementowałem w 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
źródło
1

W celu porównania dwóch obiektów najprostszym sposobem byłoby dodanie unikalnej właściwości do jednego z obiektów w momencie, gdy trzeba porównać obiekty, sprawdzenie, czy właściwość istnieje w drugim, a następnie ponowne jej usunięcie. Oszczędza to zastępowanie prototypów.

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
źródło