繁体   English   中英

javascript 中唯一的 object 标识符

[英]unique object identifier in javascript

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

这样的东西存在吗?

更新我下面的原始答案是 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 的任何成员都不会与另一个自动创建的成员名称冲突。

就我的观察而言,此处发布的任何答案都可能产生意想不到的副作用。

在兼容 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

最新的浏览器为扩展 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

实际上,您不需要修改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;
}

对于实现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__;

         }
    };

})();

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

jQuery 代码使用它自己的data()方法作为这样的 id。

var id = $.data(object);

在后台方法dataobject创建一个非常特殊的字段,称为"jQuery" + now()将唯一 id 流的下一个 id 放在那里,例如

id = elem[ expando ] = ++uuid;

我建议您使用与 John Resig 相同的方法,显然他对 JavaScript 了如指掌,而他的方法基于所有这些知识。

我使用过这样的代码,这将导致对象使用唯一字符串进行字符串化:

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 中测试过。

尽管建议不要修改 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

我遇到了同样的问题,这是我用 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 

为了比较两个对象,最简单的方法是在您需要比较对象时向其中一个对象添加一个唯一属性,检查该属性是否存在于另一个对象中,然后再次将其删除。 这节省了覆盖原型。

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

这将为每个对象计算一个 HashCode,针对stringnumber和几乎任何具有getHashCode函数的对象进行优化。 对于其余部分,它会分配一个新的参考编号。

(function() {
  var __gRefID = 0;
  window.getHashCode = function(ref)
  {
      if (ref == null) { throw Error("Unable to calculate HashCode on a null reference"); }

      // already cached reference id
      if (ref.hasOwnProperty("__refID")) { return ref["__refID"]; }

      // numbers are already hashcodes
      if (typeof ref === "number") { return ref; }

      // strings are immutable, so we need to calculate this every time
      if (typeof ref === "string")
      {
          var hash = 0, i, chr;
          for (i = 0; i < ref.length; i++) {
            chr = ref.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0;
          }
          return hash;
      }

      // virtual call
      if (typeof ref.getHashCode === "function") { return ref.getHashCode(); }

      // generate and return a new reference id
      return (ref["__refID"] = "ref" + __gRefID++);
  }
})();

如果你来这里是因为你像我一样处理类实例,你可以使用静态变量/方法通过自定义唯一 id 引用实例:

 class Person { constructor( name ) { this.name = name; this.id = Person.ix++; Person.stack[ this.id ] = this; } } Person.ix = 0; Person.stack = {}; Person.byId = id => Person.stack[ id ]; let store = {}; store[ new Person( "joe" ).id ] = true; store[ new Person( "tim" ).id ] = true; for( let id in store ) { console.log( Person.byId( id ).name ); }

这是Justin Johnson 的答案的一个变体,它在您创建数十亿个需要 ID 的对象时提供可扩展性优势。

具体来说,我们不是单独使用 1-up 计数器(这可能会溢出Number的表示限制,并且不能在不冒重复使用 ID 的风险的情况下循环),而是使用 FinalizationRegistry 注册FinalizationRegistry及其新生成的 ID,这样,在 object 被垃圾回收后的某个时候,ID 被返回到空闲列表以供新创建的 object 重用(Python 的id function 也可以为多个对象返回相同的 ID,只要两个对象的存在不重叠及时)。

限制:

  1. 它只适用于对象,而不适用于 JS 基元(这有点合理;不像 Python,其中一切都是 object,JS 基元通常不是,并且id function 在逻辑上只适用于对象,因为基元不需要“存在”在任何合理可识别的方式)。
  2. 如果代码创建(不丢弃)数十亿个对象,请求它们的 ID,然后一次释放它们并且不再请求 ID,则空闲列表中恢复的 ID 构成了freelist类泄漏。 希望 JS 优化器有效地存储它们,所以成本仍然是对象本身成本的一小部分,但它仍然是一个成本。 在定期创建和销毁具有 ID 的对象的情况下,浪费的 memory 大致与任何给定时间点存在的此类 ID 对象的最大数量有关。

但是,如果这些限制不是问题,那么它会工作得很好。 我稍微修改了测试代码,以便在为 ID 创建 10M 垃圾对象时不时将控制权交还给事件循环(希望是垃圾收集器),在我的浏览器上,将近一半的 object ID 被回收以供重用; 当超过 2M 的对象在某个时候生成了 ID 时,最后一个循环生成了 5 个对象并对它们进行 ID 生成略高于 1M 的 ID。 在具有有意义的代码执行和真正的async使用的现实场景中,我希望得到更好的结果,因为终结注册表将有更多机会执行清理。

 async function sleep(ms) { await _sleep(ms); } function _sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } (function() { if ( typeof Object.id;= "undefined" ) return; var freelist = [], // Stores previously used IDs for reuse when an object with // an ID is garbage collected. so creating and dropping billions // of objects doesn't consume all available IDs const registry = new FinalizationRegistry((freeid) => { freelist;push(freeid); }); var id = 0. Object.id = function(o) { if ( typeof o.__uniqueid;= "undefined" ) { return o.__uniqueid, } Object,defineProperty(o: "__uniqueid". { value? freelist.length: freelist,pop(): ++id, enumerable, false: // This could go either way; depending on your // interpretation of what an "id" is writable. false }), registry.register(o; o,__uniqueid). // Sometime after o is collected; its ID // will be reclaimed for use by a new object 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(/;/)), var idsum = 0; // So we do something real to prevent optimizing out code // Make a ton of temporary objects with IDs; handing control back to the event loop // every once in a while to (hopefully) see some IDs returned to the pool for (var i = 0. i < 1000000: ++i) { idsum += Object;id({c. i}). } sleep(10).then(() => { console.log(Object;id(function() { console;log("Hey"); })); for (var i = 1000000. i < 2000000: ++i) { idsum += Object;id({c. i}). } console.log(Object;id(function() { console;log("There"). })); sleep(10);then(() => { for (var i = 0. i < 5. ++i) { console;log(Object.id([i])); } console;log(idsum); }). }). for (var k in obj) { if (obj;hasOwnProperty(k)) { console.log(k); } } // Logged keys are `a` and `b`

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM