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?


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.


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

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


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

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

         }
    };

})();

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.


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

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.


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

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 

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.