уникальный идентификатор объекта в javascript

121

Мне нужно провести некоторый эксперимент, и мне нужно знать какой-то уникальный идентификатор для объектов в javascript, чтобы я мог видеть, совпадают ли они. Я не хочу использовать операторы равенства, мне нужно что-то вроде функции id () в python.

Есть ли что-то подобное?

Стефано Борини
источник
2
Мне немного любопытно, почему вы хотите избегать операторов равенства?
CMS
22
потому что я хочу чего-то простого, и я хочу видеть число, что-то ясное. Этот язык заставляет котенка плакать, я уже достаточно борюсь с этим.
Стефано Борини
4
Оператор строгого равенства (===) будет делать то, что вы просите, с объектами (если вы сравниваете числа / строки и т. Д., Это не одно и то же), и это проще, чем создавать секретный уникальный идентификатор для каждого объекта.
Бен Зотто
4
@CMS @Ben Наличие уникальных идентификаторов может быть полезно для отладки или реализации таких вещей, как IdentitySet.
Alex Jasmin
9
Хочу сообщить, что я совершил прорыв в понимании JavaScript. вроде закрыл синапс. Теперь все ясно. Я кое-что видел. Я получил уровень программиста javascript.
Стефано Борини

Ответы:

69

Обновление Мой первоначальный ответ ниже был написан 6 лет назад в стиле, соответствующем времени и моему пониманию. В ответ на некоторый разговор в комментариях, более современный подход к этому заключается в следующем:

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

Если у вас устаревшие требования к браузеру, проверьте здесь совместимость с браузером для Object.defineProperty.

Исходный ответ приведен ниже (а не просто в истории изменений), потому что я думаю, что сравнение ценное.


Вы можете попробовать следующее. Это также дает вам возможность явно установить идентификатор объекта в его конструкторе или где-либо еще.

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

Позаботьтесь о том, чтобы любой член, который вы используете для внутреннего хранения уникального идентификатора, не конфликтует с другим автоматически созданным именем члена.

Джастин Джонсон
источник
1
@Justin Добавление свойств в Object.prototype проблематично в ECMAScript 3, поскольку эти свойства перечислимы для всех объектов. Таким образом, если вы определяете Object.prototype.a, тогда "a" будет виден, когда вы сделаете for (prop in {}) alert (prop); Таким образом, вы должны найти компромисс между расширением Object.prototype и возможностью перебирать объекты, подобные записи, с помощью цикла for..in. Это серьезная проблема для библиотек,
Алекс Жасмин
29
Никаких компромиссов. Это давно считается передовой практикой всегда использовать object.hasOwnProperty(member)при использовании for..inцикла. Это хорошо задокументированная практика, и она соблюдается jslint
Джастин Джонсон,
3
Ни один из них не рекомендовал бы делать это также, по крайней мере, не для каждого объекта, вы можете сделать то же самое только с объектами, которые вы обрабатываете. к сожалению, большую часть времени нам приходится использовать внешние библиотеки javascript, и, к сожалению, не все из них хорошо запрограммированы, поэтому избегайте этого, если у вас нет полного контроля над всеми библиотеками, включенными на вашу веб-страницу, или, по крайней мере, вы знаете, что они хорошо обрабатываются ,
бесполезно
1
@JustinJohnson: Существует компромисс в ES5: defineProperty(…, {enumerable:false}). И сам uidметод в Objectлюбом случае должен быть в пространстве имен
Берги
@JustinJohnson, если предположить, что идентификатор объекта был таким, 4как бы вы затем назначили obj с идентификатором 4 переменной, чтобы вы могли что-то с ней делать ... например, обращаться к ее свойствам?
Сэр
51

Насколько я понимаю, любой ответ, опубликованный здесь, может иметь неожиданные побочные эффекты.

В среде, совместимой с ES2015, вы можете избежать побочных эффектов с помощью 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
источник
1
А почему бы и нет return currentId?
Густ ван де Валь
Почему WeakMapв отличие от Map? Эта функция выйдет из строя, если вы предоставите ей строку или число.
Nate Symer
35

Последние версии браузеров предоставляют более чистый метод расширения Object.prototype. Этот код сделает свойство скрытым от перечисления свойств (для p in o)

Для браузеров, реализующих defineProperty , вы можете реализовать свойство uniqueId следующим образом:

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

Подробнее см. Https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty.

Александр Тотич
источник
2
«Последние браузеры» явно не включают Firefox 3.6. (Да, я отказываюсь от участия в гонке обновлений в более новых версиях Firefox, и я уверен, что я не единственный. Кроме того, FF3.6 всего 1 год.)
Барт,
7
1 год не только в этом виде спорта. Интернет динамично развивается - это хорошо. Современные браузеры имеют автоматические средства обновления именно для этого.
Kos
1
Несколько лет спустя это, кажется, работает для меня лучше, чем принятый ответ.
Fitter Man
11

Собственно, вам не нужно изменять objectпрототип и добавлять туда функцию. Следующее должно хорошо работать для вашей цели.

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
источник
4
nocase, snake_case и camelCase объединили все три фрагмента кода в 6-строчный фрагмент кода. Вы не видите это каждый день
Густ ван де Валь
это кажется гораздо более убедительным механизмом, чем objectхаки. вам нужно запускать его только тогда, когда вы хотите, чтобы объект OP совпал, и он не вмешивается в остальное время.
JL Peyret,
6

Для браузеров, реализующих этот Object.defineProperty()метод, приведенный ниже код генерирует и возвращает функцию, которую можно привязать к любому объекту, которым вы владеете.

Преимущество этого подхода в том, что он не расширяется Object.prototype.

Код работает, проверяя, имеет ли данный объект __objectID__свойство, и определяя его как скрытое (неперечисляемое) свойство, доступное только для чтения, если нет.

Таким образом, он защищен от любых попыток изменить или переопределить obj.__objectID__свойство только для чтения после того, как оно было определено, и последовательно выдает приятную ошибку вместо тихой ошибки.

Наконец, в весьма крайнем случае, когда __objectID__для данного объекта уже был определен какой-то другой код , это значение просто будет возвращено.

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
источник
4

Код jQuery использует собственный data()метод в качестве идентификатора.

var id = $.data(object);

В методе за кулисами dataсоздается очень специальное поле с objectименем "jQuery" + now()put there next id потока уникальных идентификаторов, например

id = elem[ expando ] = ++uuid;

Я бы посоветовал вам использовать тот же метод, поскольку Джон Ресиг, очевидно, знает все о JavaScript, и его метод основан на всех этих знаниях.

Вава
источник
5
У dataметода jQuery есть недостатки. См., Например, stackoverflow.com/questions/1915341/… . Кроме того, Джон Ресиг отнюдь не знает всего, что нужно знать о JavaScript, и вера в то, что он знает, не поможет вам как разработчику JavaScript.
Тим Даун
1
@Tim, у него столько же недостатков с точки зрения получения уникального идентификатора, как и у любого другого метода, представленного здесь, поскольку он делает примерно то же самое под капотом. И да, я считаю, что Джон Ресиг знает намного больше, чем я, и я должен учиться на его решениях, даже если он не Дуглас Крокфорд.
vava
AFAIK $ .data не работает с объектами JavaScript, а только с элементами DOM.
mb21
4

Типографская версия ответа @justin, совместимая с ES6, с использованием символов для предотвращения конфликта клавиш и добавленная в глобальный Object.id для удобства. Просто скопируйте и вставьте приведенный ниже код или поместите его в импортируемый файл ObjecId.ts.

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

Пример использования:

console.log(Object.id(myObject));
Флавиен Волкен
источник
1

Я использовал такой код, который заставит объекты объединяться с уникальными строками:

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

эти __proto__биты , чтобы сохранить __id__геттер от просмотра в объекте. это было проверено только в firefox.

Эрик Стром
источник
Только учтите, что __defineGetter__это нестандартно.
Томаш Зато - Восстановите Монику
1

Несмотря на совет не изменять Object.prototype, это может быть действительно полезно для тестирования в ограниченных пределах. Автор принятого ответа изменил его, но все еще настраивает Object.id, что для меня не имеет смысла. Вот фрагмент, который делает эту работу:

// 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
источник
1

Я столкнулся с той же проблемой, и вот решение, которое я реализовал с 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

С целью сравнения двух объектов самый простой способ сделать это - добавить уникальное свойство к одному из объектов в тот момент, когда вам нужно сравнить объекты, проверить, существует ли свойство в другом, а затем удалить его снова. Это позволяет сэкономить на прототипах.

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
источник