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