JavaScript中的唯一对象标识符


119

我需要做一些实验,我需要知道javascript中对象的某种唯一标识符,因此我可以查看它们是否相同。我不想使用相等运算符,我需要python中的id()函数之类的东西。

是否存在这样的东西?


2
我有点好奇,为什么要避免使用等于运算符?
CMS 2010年

22
因为我想要简单的东西,并且想要看到一个数字,清楚的东西。这种语言使小猫哭泣,我已经在战斗了。
Stefano Borini 2010年

4
严格相等运算符(===)可以满足您对对象的要求(如果您要比较数字/字符串/等,则不一样),并且比在每个对象中构建秘密的唯一ID更简单。
本·佐托

4
@CMS @Ben具有唯一的ID对于调试或实现诸如IdentitySet之类的事情可能很有用。
Alex Jasmin'1

9
我想传达的是,我在理解javascript方面取得了突破。封闭了一个突触。现在一切都清楚了。我看过东西 我获得了JavaScript程序员的水平。
Stefano Borini 2010年

Answers:


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

原始答案保留在下面(而不是仅在更改历史记录中),因为我认为比较很有价值。


您可以进行以下调整。这也使您可以选择在其构造函数或其他地方显式设置对象的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在ECMAScript 3中向Object.prototype添加属性是有问题的,因为这些属性在所有对象上都是可枚举的。因此,如果您定义Object.prototype.a,则当您为{prop中的{}} alert(prop)进行操作时,“ a”将可见 因此,您必须在增强Object.prototype与使用for..in循环遍历类似记录的对象之间做出折衷。对于图书馆而言,这是一个严重的问题
Alex Jasmin 2010年

29
没有妥协。长期以来,一直被认为是object.hasOwnProperty(member)在使用for..in循环时始终使用的最佳实践。这是一个有据可查的做法,是由jslint实施的
Justin Johnson

3
我都不建议这样做,至少不是对于每个对象,您都可以对要处理的对象进行相同的操作。不幸的是,大多数时候我们必须使用外部javascript库,但是不幸的是,并不是每个脚本库都经过良好的编程,因此,除非您完全控制了网页中包含的所有库,或者至少您知道它们处理得当,否则请避免这种情况。 。
没用的

1
@JustinJohnson:ES5中有一个折衷方案:defineProperty(…, {enumerable:false})。而且uid方法本身也应该在Object名称空间中
Bergi 2012年

@JustinJohnson,如果可以说对象id是4您将如何将id为4的obj分配给变量,以便可以对其进行处理...就像访问其属性一样?
2015年

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呢?如果为它提供字符串或数字,该函数将崩溃。
Nate Symer

35

最新的浏览器提供了一种更干净的方法来扩展Object.prototype。此代码将从属性枚举中隐藏该属性(对于o中的p)

对于实现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行代码段中。您每天都看不到
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()方法作为此类id。

var id = $.data(object);

在后台方法中,data创建一个非常特殊的字段,该字段object称为"jQuery" + now()唯一ID流的下一个ID,例如

id = elem[ expando ] = ++uuid;

我建议您使用与John Resig显然了解JavaScript有关的所有方法,并且他的方法基于所有这些知识。


5
jQuery的data方法有缺陷。参见例如stackoverflow.com/questions/1915341/…。另外,John Resig绝对不了解JavaScript,并且相信他不会帮助您成为JavaScript开发人员。
Tim

1
@Tim,在获取唯一ID方面与在此介绍的任何其他方法一样,存在许多缺陷,因为它在后台执行的操作大致相同。是的,我相信John Resig比我了解的更多,即使他不是Douglas Crockford,我也应该从他的决定中学到东西。
vava 2010年

AFAIK $ .data不适用于JavaScript对象,仅适用于DOM元素。
mb21 2014年

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__是非标准的。
托马什Zato -恢复莫妮卡

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.