identifiant d'objet unique en javascript


121

J'ai besoin de faire des expériences et j'ai besoin de connaître une sorte d'identifiant unique pour les objets en javascript, afin que je puisse voir s'ils sont identiques. Je ne veux pas utiliser d'opérateurs d'égalité, j'ai besoin de quelque chose comme la fonction id () en python.

Est-ce que quelque chose comme ça existe?


2
Je suis un peu curieux, pourquoi voulez-vous éviter les opérateurs d'égalité?
CMS

22
parce que je veux quelque chose de simple, et je veux voir un nombre, quelque chose de clair. Cette langue fait pleurer le chaton, je la combat déjà assez.
Stefano Borini le

4
L'opérateur d'égalité stricte (===) fera ce que vous demandez sur les objets (si vous comparez des nombres / chaînes / etc ce n'est pas la même chose), et est plus simple que de construire un ID unique secret dans chaque objet.
Ben Zotto le

4
@CMS @Ben Avoir des identifiants uniques peut être utile pour le débogage ou l'implémentation de choses comme un IdentitySet.
Alex Jasmin le

9
Je souhaite communiquer que j'ai fait une percée dans la compréhension de javascript. sorte de fermé une synapse. Maintenant, tout est clair. J'ai vu des choses. J'ai gagné un niveau en programmeur javascript.
Stefano Borini

Réponses:


69

Mise à jour Ma réponse originale ci-dessous a été écrite il y a 6 ans dans un style qui correspond à l'époque et à ma compréhension. En réponse à certaines conversations dans les commentaires, une approche plus moderne est la suivante:

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

Si vous avez des exigences de navigateur archaïques, vérifiez ici la compatibilité du navigateur pour Object.defineProperty.

La réponse originale est conservée ci-dessous (au lieu de juste dans l'historique des modifications) car je pense que la comparaison est précieuse.


Vous pouvez faire tourner ce qui suit. Cela vous donne également la possibilité de définir explicitement l'ID d'un objet dans son constructeur ou ailleurs.

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

Veillez à ce que le membre que vous utilisez pour stocker en interne l'ID unique n'entre pas en conflit avec un autre nom de membre créé automatiquement.


1
@Justin L'ajout de propriétés à Object.prototype est problématique dans ECMAScript 3 car ces propriétés sont énumérables sur tous les objets. Donc si vous définissez Object.prototype.a alors "a" sera visible lorsque vous le ferez pour (prop in {}) alert (prop); Vous devez donc faire un compromis entre augmenter Object.prototype et pouvoir parcourir des objets comme des enregistrements en utilisant une boucle for..in. C'est un problème sérieux pour les bibliothèques
Alex Jasmin

29
Il n'y a pas de compromis. Il a longtemps été considéré comme une bonne pratique à toujours utiliser object.hasOwnProperty(member)lors de l'utilisation d'une for..inboucle. Il s'agit d'une pratique bien documentée et appliquée par jslint
Justin Johnson

3
ni l'un ni l'autre ne recommanderais de le faire aussi, du moins pas pour chaque objet, vous pouvez faire de même avec les objets que vous manipulez. malheureusement, la plupart du temps, nous devons utiliser des bibliothèques javascript externes, et malheureusement toutes ne sont pas bien programmées, alors évitez cela à moins que vous n'ayez le contrôle total de toutes les bibliothèques incluses dans votre page Web, ou du moins que vous sachiez qu'elles ont bien géré .
inutile

1
@JustinJohnson: Il y a un compromis à ES5: defineProperty(…, {enumerable:false}). Et la uidméthode elle-même devrait de toute façon être dans l' Objectespace de noms
Bergi

@JustinJohnson si disons que l'ID de l'objet était 4comment attribueriez-vous alors obj avec l'id 4 à une variable afin que vous puissiez faire des choses avec lui ... comme accéder à ses propriétés?
Sir

51

En ce qui concerne mon observation, toute réponse publiée ici peut avoir des effets secondaires inattendus.

Dans un environnement compatible ES2015, vous pouvez éviter tout effet secondaire en utilisant 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
Pourquoi pas return currentId?
Gust van de Wal

Pourquoi WeakMappar opposition à Map? Cette fonction plantera si vous lui fournissez une chaîne ou un nombre.
Nate Symer

35

Les derniers navigateurs fournissent une méthode plus propre pour étendre Object.prototype. Ce code rendra la propriété cachée de l'énumération des propriétés (pour p in o)

Pour les navigateurs qui implémentent defineProperty , vous pouvez implémenter la propriété uniqueId comme ceci:

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

Pour plus de détails, consultez https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty


2
"Derniers navigateurs" n'inclut apparemment pas Firefox 3.6. (Oui, je me désengage de la course à la mise à niveau dans les nouvelles versions de Firefox, et je suis sûr que je ne suis pas le seul. De plus, FF3.6 n'a que 1 an.)
bart

7
1 an n'est pas "seulement" dans ce sport. Le Web évolue de manière dynamique - c'est une bonne chose. Les navigateurs modernes ont des mises à jour automatiques précisément dans le but de permettre cela.
Kos

1
Quelques années plus tard, cela semble mieux fonctionner pour moi que la réponse acceptée.
Fitter Man

11

En fait, vous n'avez pas besoin de modifier le objectprototype et d'y ajouter une fonction. Ce qui suit devrait bien fonctionner pour votre objectif.

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 et camelCase ont tous les trois fait leur chemin dans un extrait de code de 6 lignes. Vous ne voyez pas ça tous les jours
Gust van de Wal

cela semble être un mécanisme beaucoup plus convaincant que les objecthacks. vous n'avez besoin de l'exécuter que lorsque vous voulez que l'objet de l'OP corresponde et qu'il reste à l'écart le reste du temps.
JL Peyret le

6

Pour les navigateurs qui implémentent la Object.defineProperty()méthode, le code ci-dessous génère et renvoie une fonction que vous pouvez lier à n'importe quel objet que vous possédez.

Cette approche a l'avantage de ne pas s'étendre Object.prototype.

Le code fonctionne en vérifiant si l'objet donné a un __objectID__ propriété et en le définissant comme une propriété en lecture seule masquée (non énumérable) dans le cas contraire.

Il est donc sûr contre toute tentative de modification ou de redéfinition de la obj.__objectID__propriété en lecture seule après qu'elle a été définie, et génère systématiquement une erreur intéressante au lieu d'échouer silencieusement.

Enfin, dans le cas assez extrême où un autre code aurait déjà été défini __objectID__sur un objet donné, cette valeur serait simplement renvoyée.

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

Le code jQuery utilise sa propre data()méthode comme tel id.

var id = $.data(object);

Dans les coulisses, la méthode datacrée un champ très spécial dans objectappelé, "jQuery" + now()placez-y l'identifiant suivant d'un flux d'identifiants uniques comme

id = elem[ expando ] = ++uuid;

Je vous suggère d'utiliser la même méthode car John Resig sait évidemment tout ce qu'il y a à propos de JavaScript et sa méthode est basée sur toutes ces connaissances.


5
La dataméthode de jQuery a des défauts. Voir par exemple stackoverflow.com/questions/1915341/… . De plus, John Resig ne sait en aucun cas tout ce qu'il y a à savoir sur JavaScript, et croire qu'il le sait ne vous aidera pas en tant que développeur JavaScript.
Tim Down le

1
@Tim, elle a autant de défauts en termes d'obtention d'identifiant unique que toute autre méthode présentée ici car elle fait à peu près la même chose sous le capot. Et oui, je crois que John Resig en sait beaucoup plus que moi et que je devrais apprendre de ses décisions même s'il n'est pas Douglas Crockford.
vava le

AFAIK $ .data ne fonctionne pas sur les objets JavaScript mais uniquement sur les éléments DOM.
mb21

4

Version typographique de @justin answer, compatible ES6, utilisant des symboles pour éviter toute collision de clé et ajoutée dans le Object.id global pour plus de commodité. Copiez-collez simplement le code ci-dessous ou mettez-le dans un fichier ObjecId.ts que vous importerez.

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

Exemple d'utilisation:

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

1

J'ai utilisé un code comme celui-ci, ce qui entraînera la stringification des objets avec des chaînes uniques:

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

les __proto__bits doivent empêcher le __id__getter de s'afficher dans l'objet. cela n'a été testé que dans Firefox.


Gardez simplement à l'esprit que ce __defineGetter__n'est pas standard.
Tomáš Zato - Réintégrer Monica le

1

Nonobstant le conseil de ne pas modifier Object.prototype, cela peut toujours être très utile pour les tests, dans une portée limitée. L'auteur de la réponse acceptée l'a changé, mais est toujours en train de régler Object.id, ce qui n'a aucun sens pour moi. Voici un extrait qui fait le travail:

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

J'ai rencontré le même problème et voici la solution que j'ai implémentée avec 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

Dans le but de comparer deux objets, le moyen le plus simple de le faire serait d'ajouter une propriété unique à l'un des objets au moment où vous devez comparer les objets, de vérifier si la propriété existe dans l'autre, puis de la supprimer à nouveau. Cela évite les prototypes de remplacement.

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.