унікальний ідентифікатор об'єкта в JavaScript


120

Мені потрібно зробити експеримент, і мені потрібно знати якийсь унікальний ідентифікатор для об’єктів у javascript, щоб я міг бачити, чи вони однакові. Я не хочу використовувати оператори рівності, мені потрібно щось на зразок функції id () в python.

Чи існує щось подібне?


2
Мені трохи цікаво, чому ви хочете уникати операторів рівності?
— CMS

22
тому що я хочу чогось простого, і хочу побачити число, щось зрозуміле. Ця мова змушує кошеня плакати, я вже досить борюся з ним.
— Стефано Борині

4
Оператор суворої рівності (===) зробить те, що ви просите на об'єктах (якщо ви порівнюєте числа / рядки / тощо, це не одне і те ж), і простіше, ніж будувати секретний унікальний ідентифікатор для кожного об'єкта.
— Бен Зотто

4
@CMS @Ben Наявність унікальних ідентифікаторів може бути корисною для налагодження чи реалізації таких речей, як IdentitySet.
— Олексій Жасмін

9
Я хочу повідомити, що я зробив прорив у розумінні JavaScript. вид закритого синапсу. Тепер все зрозуміло. Я бачив речі. Я здобув рівень у програмі JavaScript.
— Стефано Борині

Відповіді:


68

Оновити Мою оригінальну відповідь нижче було написано 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" буде видно, коли ви робите для (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 з id 4 змінної, щоб ви могли робити з нею речі, як-от Acess його властивості?
— Сер

50

Наскільки моє спостереження, будь-яка відповідь, розміщена тут, може мати несподівані побічні ефекти.

У сумісній з 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? Ця функція припинить роботу, якщо ви надасте їй рядок або число.
— Нейт Симер

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 рік не є "лише" в цьому виді спорту. Інтернет розвивається динамічно - це добре. Сучасні браузери мають автоматичні оновлення саме для того, щоб це допустити.
— Кос

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()метод як такий id.

var id = $.data(object);

За кулісним методом dataстворюється дуже спеціальне поле у objectвиклику "jQuery" + now()поставленого наступного ідентифікатора потоку унікальних ідентифікаторів на зразок

id = elem[ expando ] = ++uuid;

Я б запропонував вам використовувати той самий метод, що і Джон Ресіг, очевидно, знає все, що існує про JavaScript, і його метод заснований на всіх цих знаннях.


5
dataМетод jQuery має недоліки. Див., Наприклад, stackoverflow.com/questions/1915341/… . Крім того, Джон Ресіг ні в якому разі не знає, що потрібно знати про JavaScript, і вірити, що він це не допоможе вам як розробнику JavaScript.
— Tim Down

1
@Tim, у нього є стільки ж недоліків щодо отримання унікального ідентифікатора, як і будь-який інший метод, представлений тут, оскільки він робить приблизно те ж саме під кришкою. І так, я вважаю, що Джон Резіг знає набагато більше, ніж я, і мені слід вчитися на його рішеннях, навіть якщо він не Дуглас Крокфорд.
— vava

AFAIK $ .data не працює на об’єктах JavaScript, а лише на елементах DOM.
— mb21

4

Версія машинопису відповіді @justin, сумісна з ES6, використовує Symbols для запобігання будь-якого зіткнення клавіш та додається в глобальний 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.