уникальный идентификатор объекта в 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

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

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

         }
    };

})();

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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.