mã định danh đối tượng duy nhất trong javascript


119

Tôi cần thực hiện một số thử nghiệm và tôi cần biết một số loại định danh duy nhất cho các đối tượng trong javascript, vì vậy tôi có thể xem chúng có giống nhau không. Tôi không muốn sử dụng các toán tử bình đẳng, tôi cần một cái gì đó như hàm id () trong python.

Có một cái gì đó như thế này tồn tại ?


2
Tôi hơi tò mò, tại sao bạn muốn tránh các toán tử bình đẳng?
CMS

22
bởi vì tôi muốn một cái gì đó đơn giản và tôi muốn thấy một con số, một cái gì đó rõ ràng. Ngôn ngữ này khiến mèo con khóc, tôi đã chiến đấu đủ rồi.
Stefano Borini

4
Toán tử bình đẳng nghiêm ngặt (===) sẽ thực hiện những gì bạn yêu cầu trên các đối tượng (nếu bạn đang so sánh số / chuỗi / v.v. thì nó không giống nhau) và đơn giản hơn là xây dựng một ID duy nhất bí mật vào mọi đối tượng.
Ben Zotto

4
@CMS @Ben Có id duy nhất có thể hữu ích cho việc gỡ lỗi hoặc triển khai những thứ như IdentitySet.
Alex Jasmin

9
Tôi muốn thông báo rằng tôi đã tạo ra một bước đột phá trong hiểu biết về javascript. kiểu đóng một khớp thần kinh. Bây giờ mọi thứ đã rõ ràng. Tôi đã thấy mọi thứ. Tôi đã đạt được một trình độ lập trình javascript.
Stefano Borini

Câu trả lời:


68

Cập nhật Câu trả lời ban đầu của tôi dưới đây được viết cách đây 6 năm theo phong cách phù hợp với thời đại và sự hiểu biết của tôi. Đáp lại một số cuộc trò chuyện trong các bình luận, một cách tiếp cận hiện đại hơn cho vấn đề này như sau:

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

Nếu bạn có các yêu cầu về trình duyệt cổ, hãy kiểm tra tại đây để biết khả năng tương thích của trình duyệt cho Object.defineProperty.

Câu trả lời ban đầu được giữ bên dưới (thay vì chỉ trong lịch sử thay đổi) vì tôi nghĩ rằng so sánh có giá trị.


Bạn có thể đưa ra một vòng quay sau đây. Điều này cũng cung cấp cho bạn tùy chọn để đặt ID của đối tượng một cách rõ ràng trong hàm tạo của nó hoặc ở nơi khác.

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

Hãy cẩn thận để đảm bảo rằng bất kỳ thành viên nào bạn sử dụng để lưu trữ nội bộ ID duy nhất sẽ không va chạm với tên thành viên được tạo tự động khác.


1
@Justin Việc thêm thuộc tính vào Object.prototype là vấn đề trong ECMAScript 3 vì những đặc điểm này có thể liệt kê được trên tất cả các đối tượng. Vì vậy, nếu bạn định nghĩa Object.prototype.a thì "a" sẽ hiển thị khi bạn thực hiện for (prop in {}) alert (prop); Vì vậy, bạn phải thỏa hiệp giữa việc tăng cường Object.prototype và có thể lặp qua bản ghi như các đối tượng bằng vòng lặp for..in. Đây là một vấn đề nghiêm trọng cho các thư viện
Alex Jasmin

29
Không có sự thỏa hiệp. Từ lâu, nó đã được coi là một phương pháp hay nhất để luôn sử dụng object.hasOwnProperty(member)khi sử dụng for..invòng lặp. Đây là một thực tế cũng ghi nhận và một trong đó là thực thi bởi JSLint
Justin Johnson

3
Tôi cũng không khuyên bạn nên làm điều này, ít nhất là không phải cho mọi đối tượng, bạn có thể làm điều tương tự chỉ với các đối tượng bạn xử lý. Thật không may, hầu hết thời gian chúng ta phải sử dụng các thư viện javascript bên ngoài và rất tiếc là không phải mọi thư viện trong số chúng đều được lập trình tốt, vì vậy hãy tránh điều này trừ khi bạn có toàn quyền kiểm soát tất cả các thư viện có trong trang web của mình, hoặc ít nhất bạn biết chúng xử lý tốt .
vô ích

1
@JustinJohnson: Có một sự thỏa hiệp trong ES5: defineProperty(…, {enumerable:false}). Và uidbản thân phương thức này dù sao cũng phải nằm trong Objectkhông gian tên
Bergi

@JustinJohnson nếu cho phép nói id đối tượng là 4như thế nào, sau đó bạn sẽ gán đối tượng với id 4 cho một biến để bạn có thể làm những việc với nó ... chẳng hạn như xem thuộc tính của nó?
Thưa ngài,

50

Theo quan sát của tôi, bất kỳ câu trả lời nào được đăng ở đây đều có thể có tác dụng phụ không mong muốn.

Trong môi trường tương thích với ES2015, bạn có thể tránh bất kỳ tác dụng phụ nào bằng cách sử dụng 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
Tại sao không return currentId?
Gust van de Wal

Tại sao WeakMaptrái ngược với Map? Hàm đó sẽ bị lỗi nếu bạn cung cấp cho nó một chuỗi hoặc số.
Nate Symer

35

Các trình duyệt mới nhất cung cấp một phương pháp rõ ràng hơn để mở rộng Object.prototype. Mã này sẽ làm cho thuộc tính ẩn khỏi liệt kê thuộc tính (đối với p in o)

Đối với các trình duyệt triển khai defineProperty , bạn có thể triển khai thuộc tính uniqueId như sau:

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

Để biết chi tiết, hãy xem https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty


2
"Các trình duyệt mới nhất" dường như không bao gồm Firefox 3.6. (Vâng, tôi đang chọn không tham gia cuộc đua nâng cấp trong các phiên bản Firefox mới hơn và tôi chắc chắn rằng tôi không phải là người duy nhất. Bên cạnh đó, FF3.6 mới chỉ được 1 năm tuổi.)
bart

7
1 tuổi không phải là "duy nhất" trong môn thể thao này. Web phát triển năng động - đó là một điều tốt. Các trình duyệt hiện đại có các trình cập nhật tự động chính xác với mục đích cho phép điều đó.
Kos,

1
Một vài năm sau, điều này dường như hoạt động tốt hơn đối với tôi so với câu trả lời được chấp nhận.
Fitter Man

11

Trên thực tế, bạn không cần phải sửa đổi objectnguyên mẫu và thêm một chức năng vào đó. Những điều sau đây sẽ hoạt động tốt cho mục đích của bạn.

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, solid_case camelCase có cả ba đã tạo thành một đoạn mã 6 dòng. Bạn không thấy điều đó hàng ngày
Gust van de Wal

đây có vẻ như là một cơ chế thuyết phục hơn nhiều so với objecthack. bạn chỉ cần chạy nó khi bạn muốn đối tượng của OP khớp và nó không hoạt động trong thời gian còn lại.
JL Peyret

6

Đối với các trình duyệt triển khai Object.defineProperty()phương thức, đoạn mã dưới đây tạo và trả về một hàm mà bạn có thể liên kết với bất kỳ đối tượng nào mà bạn sở hữu.

Cách làm này có ưu điểm là không kéo dài Object.prototype.

Mã hoạt động bằng cách kiểm tra xem đối tượng đã cho có thuộc __objectID__tính hay không và bằng cách xác định nó là thuộc tính chỉ đọc ẩn (không thể liệt kê) nếu không.

Vì vậy, sẽ an toàn trước mọi nỗ lực thay đổi hoặc xác định lại thuộc tính chỉ đọc obj.__objectID__sau khi nó đã được xác định và liên tục ném ra một lỗi tốt thay vì âm thầm thất bại.

Cuối cùng, trong trường hợp cực đoan khi một số mã khác đã được xác định __objectID__trên một đối tượng nhất định, giá trị này sẽ đơn giản được trả về.

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

Mã jQuery sử dụng data()phương thức riêng của nó làm id như vậy.

var id = $.data(object);

Ở phương pháp hậu trường datatạo ra một trường rất đặc biệt objectđược gọi là "jQuery" + now()đặt ở đó id tiếp theo của một luồng id duy nhất như

id = elem[ expando ] = ++uuid;

Tôi khuyên bạn nên sử dụng phương pháp tương tự vì John Resig rõ ràng biết tất cả những gì có về JavaScript và phương pháp của anh ấy dựa trên tất cả kiến ​​thức đó.


5
dataPhương thức của jQuery có sai sót. Xem ví dụ: stackoverflow.com/questions/1915341/… . Ngoài ra, John Resig hoàn toàn không biết tất cả những gì cần biết về JavaScript và tin rằng anh ấy sẽ không giúp bạn với tư cách là một nhà phát triển JavaScript.
Tim Down

1
@Tim, nó có nhiều sai sót về mặt nhận id duy nhất như bất kỳ phương pháp nào khác được trình bày ở đây vì nó hoạt động gần giống nhau. Và vâng, tôi tin rằng John Resig biết nhiều hơn tôi và tôi nên học hỏi từ những quyết định của anh ấy ngay cả khi anh ấy không phải là Douglas Crockford.
vava

AFAIK $ .data không hoạt động trên các đối tượng JavaScript mà chỉ hoạt động trên các phần tử DOM.
mb21

4

Phiên bản Typescript của câu trả lời @justin, tương thích với ES6, sử dụng Symbols để ngăn chặn mọi va chạm chính và được thêm vào Object.id toàn cầu để thuận tiện. Chỉ cần sao chép, dán mã bên dưới hoặc đặt nó vào tệp ObjecId.ts mà bạn sẽ nhập.

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

Ví dụ về cách sử dụng:

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

1

Tôi đã sử dụng mã như thế này, điều này sẽ khiến các Đối tượng xâu chuỗi bằng các chuỗi duy nhất:

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

các __proto__bit để giữ cho __id__getter không hiển thị trong đối tượng. điều này chỉ được thử nghiệm trong firefox.


Chỉ cần ghi nhớ rằng đó __defineGetter__không phải là tiêu chuẩn.
Tomáš Zato - Phục hồi Monica,

1

Bất chấp lời khuyên không nên sửa đổi Object.prototype, điều này vẫn có thể thực sự hữu ích cho việc thử nghiệm, trong một phạm vi hạn chế. Tác giả của câu trả lời được chấp nhận đã thay đổi nó, nhưng vẫn đang thiết lập Object.id, điều này không có ý nghĩa đối với tôi. Đây là một đoạn mã thực hiện công việc:

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

Tôi gặp phải vấn đề tương tự và đây là giải pháp tôi đã triển khai với 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

Với mục đích so sánh hai đối tượng, cách đơn giản nhất để thực hiện việc này là thêm một thuộc tính duy nhất vào một trong các đối tượng tại thời điểm bạn cần so sánh các đối tượng, kiểm tra xem thuộc tính có tồn tại trong đối tượng kia hay không và sau đó xóa nó một lần nữa. Điều này giúp tiết kiệm các nguyên mẫu ghi đè.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.