ตัวระบุวัตถุที่ไม่ซ้ำกันในจาวาสคริปต์


121

ฉันต้องทำการทดลองบางอย่างและฉันจำเป็นต้องทราบตัวระบุที่ไม่ซ้ำกันบางประเภทสำหรับออบเจ็กต์ในจาวาสคริปต์เพื่อที่ฉันจะได้เห็นว่ามันเหมือนกันหรือไม่ ฉันไม่ต้องการใช้ตัวดำเนินการความเท่าเทียมกันฉันต้องการบางอย่างเช่นฟังก์ชัน id () ใน python

สิ่งนี้มีอยู่จริงหรือไม่?


2
ฉันสงสัยเล็กน้อยทำไมคุณถึงต้องการหลีกเลี่ยงตัวดำเนินการความเท่าเทียมกัน
CMS

22
เพราะฉันต้องการอะไรง่ายๆและฉันต้องการเห็นตัวเลขบางอย่างที่ชัดเจน ภาษานี้ทำให้ลูกแมวร้องไห้ฉันต่อสู้กับมันมากพอแล้ว
Stefano Borini

4
ตัวดำเนินการความเท่าเทียมกันอย่างเข้มงวด (===) จะทำสิ่งที่คุณขอกับวัตถุ (หากคุณกำลังเปรียบเทียบตัวเลข / สตริง / ฯลฯ มันไม่ใช่สิ่งเดียวกัน) และง่ายกว่าการสร้างรหัสลับเฉพาะในทุกวัตถุ
Ben Zotto

4
@CMS @Ben การมีรหัสเฉพาะจะมีประโยชน์สำหรับการดีบักหรือใช้งานสิ่งต่างๆเช่น IdentitySet
Alex Jasmin

9
ฉันต้องการสื่อสารว่าฉันได้พัฒนาความเข้าใจในจาวาสคริปต์แล้ว ชนิดของไซแนปส์ปิด ตอนนี้ทุกอย่างชัดเจน ฉันเคยเห็นสิ่งต่างๆ ฉันได้รับระดับหนึ่งในโปรแกรมเมอร์จาวาสคริปต์
Stefano Borini

คำตอบ:


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สำหรับความเข้ากันได้สำหรับเบราว์เซอร์

คำตอบเดิมถูกเก็บไว้ด้านล่าง (แทนที่จะอยู่ในประวัติการเปลี่ยนแปลง) เพราะฉันคิดว่าการเปรียบเทียบมีคุณค่า


คุณสามารถหมุนได้ดังต่อไปนี้ นอกจากนี้ยังช่วยให้คุณมีตัวเลือกในการตั้งค่า ID ของวัตถุอย่างชัดเจนในตัวสร้างหรือที่อื่น ๆ

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

ดูแลให้แน่ใจว่าสมาชิกที่คุณใช้เก็บ ID เฉพาะไว้ภายในจะไม่ชนกับชื่อสมาชิกอื่นที่สร้างขึ้นโดยอัตโนมัติ


1
@Justin การเพิ่มคุณสมบัติให้กับ Object.prototype เป็นปัญหาใน ECMAScript 3 เนื่องจากคุณสมบัติเหล่านี้สามารถแจกแจงได้บนวัตถุทั้งหมด ดังนั้นหากคุณกำหนดObject.prototype.aแล้ว "a" จะปรากฏให้เห็นเมื่อคุณทำสำหรับ (prop in {}) alert (prop); ดังนั้นคุณต้องทำการประนีประนอมระหว่างการเพิ่มObject.prototypeและความสามารถในการทำซ้ำผ่านบันทึกเช่นเดียวกับวัตถุโดยใช้ for..in loop นี่เป็นปัญหาร้ายแรงสำหรับห้องสมุด
Alex Jasmin

29
ไม่มีการประนีประนอม ถือเป็นแนวทางปฏิบัติที่ดีที่สุดมานานแล้วobject.hasOwnProperty(member)เมื่อใช้for..inลูป นี่เป็นแนวปฏิบัติที่ได้รับการบันทึกไว้เป็นอย่างดีและเป็นแนวทางที่บังคับใช้โดย jslint
Justin Johnson

3
ฉันไม่แนะนำให้ทำเช่นนี้ด้วยอย่างน้อยก็ไม่ใช่สำหรับทุกวัตถุคุณสามารถทำเช่นเดียวกันกับวัตถุที่คุณจัดการได้ น่าเสียดายที่ส่วนใหญ่เราต้องใช้ไลบรารี javascript ภายนอกและน่าเสียดายที่ไม่ใช่ทุกไลบรารีที่ได้รับการตั้งโปรแกรมอย่างดีดังนั้นหลีกเลี่ยงสิ่งนี้เว้นแต่คุณจะควบคุมไลบรารีทั้งหมดที่รวมอยู่ในหน้าเว็บของคุณได้ทั้งหมดหรืออย่างน้อยคุณก็รู้ว่ามันจัดการได้ดี .
ไร้ประโยชน์

1
@JustinJohnson: มีการประนีประนอมใน ES5 defineProperty(…, {enumerable:false})คือ: และuidวิธีการเองก็ควรอยู่ในObjectเนมสเปซอยู่ดี
Bergi

@JustinJohnson ถ้าให้บอกว่ารหัสวัตถุเป็น4อย่างไรคุณจะกำหนด obj ด้วย id 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?
Gust van de Wal

ทำไมWeakMapตรงข้ามกับMap? ฟังก์ชันนั้นจะหยุดทำงานหากคุณระบุสตริงหรือตัวเลข
Nate Symer

35

เบราว์เซอร์ล่าสุดมีวิธีการที่สะอาดกว่าในการขยาย Object.prototype รหัสนี้จะทำให้คุณสมบัติถูกซ่อนจากการแจงนับคุณสมบัติ (สำหรับ p ใน 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 ปีเท่านั้น)
bart

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 บรรทัด คุณไม่ได้เห็นทุกวัน
Gust van de Wal

ดูเหมือนว่าจะเป็นกลไกที่น่าเชื่อมากกว่าการ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()ใส่รหัสถัดไปของสตรีมของรหัสเฉพาะเช่น

id = elem[ expando ] = ++uuid;

ฉันขอแนะนำให้คุณใช้วิธีการเดียวกับที่ John Resig รู้ดีว่ามีทั้งหมดเกี่ยวกับ JavaScript และวิธีการของเขาขึ้นอยู่กับความรู้ทั้งหมดนั้น


5
dataวิธีการของ jQuery มีข้อบกพร่อง ดูตัวอย่างstackoverflow.com/questions/1915341/... นอกจากนี้ John Resig ไม่ได้รู้ทุกอย่างเกี่ยวกับ JavaScript และเชื่อว่าเขาจะไม่ช่วยคุณในฐานะนักพัฒนา JavaScript
Tim Down

1
@ ทิมมีข้อบกพร่องมากพอ ๆ กับการได้รับรหัสเฉพาะเช่นเดียวกับวิธีการอื่น ๆ ที่นำเสนอที่นี่เนื่องจากมันทำประมาณเดียวกันภายใต้ประทุน และใช่ฉันเชื่อว่า John Resig รู้มากกว่าฉันมากและฉันควรเรียนรู้จากการตัดสินใจของเขาแม้ว่าเขาจะไม่ใช่ Douglas Crockford ก็ตาม
vava

AFAIK $ .data ใช้ไม่ได้กับวัตถุ JavaScript แต่มีเพียงองค์ประกอบ DOM เท่านั้น
mb21

4

คำตอบ @justin รุ่น typescript เข้ากันได้กับ 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

ฉันเคยใช้รหัสแบบนี้ซึ่งจะทำให้ Objects ผูกด้วยสตริงเฉพาะ:

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__getter แสดงในวัตถุ สิ่งนี้ได้รับการทดสอบใน firefox เท่านั้น


เพียงจำไว้ว่าไม่ได้__defineGetter__มาตรฐาน
Tomáš Zato - คืนสถานะ Monica

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.