【问题标题】:unique object identifier in javascriptjavascript中的唯一对象标识符
【发布时间】:2010-01-04 05:34:50
【问题描述】:

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

这样的东西存在吗?

【问题讨论】:

  • 我有点好奇,你为什么要避免使用相等运算符?
  • 因为我想要一些简单的东西,我想看到一个数字,一些清晰的东西。这种语言让小猫哭了,我已经够拼了。
  • 严格相等运算符 (===) 将按照您对对象的要求执行操作(如果您要比较数字/字符串/等,则不是同一件事),并且比构建秘密更简单每个对象的唯一 ID。
  • @CMS @Ben 拥有唯一的 id 对于调试或实现诸如 IdentitySet 之类的东西很有用。
  • 我想传达我在对javascript的理解上做了一个突破。一种关闭突触。现在一切都清楚了。我见过东西。我获得了 javascript 程序员的水平。

标签: javascript


【解决方案1】:

更新我下面的原始答案是6年前写的,风格符合时代和我的理解。针对 cme​​ts 中的一些对话,一种更现代的方法如下:

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

如果您有过时的浏览器要求,请check here 以获得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 的任何成员都不会与另一个自动创建的成员名称冲突。

【讨论】:

  • @Justin 在 ECMAScript 3 中向 Object.prototype 添加属性是有问题的,因为这些属性在所有对象上都是可枚举的。因此,如果您定义 Object.prototype.a 那么当您执行 for (prop in {}) alert(prop); 时“a”将可见,因此您必须增强 Object.prototype 和能够使用 for..in 循环遍历记录之类的对象之间的折衷。这对图书馆来说是一个严重的问题
  • 没有妥协。长期以来,在使用 for..in 循环时始终使用 object.hasOwnProperty(member) 一直被认为是最佳实践。这是一个有据可查的实践,由 jslint 强制执行
  • 我都不建议这样做,至少不是对每个对象,你可以对你处理的对象做同样的事情。不幸的是,大多数时候我们必须使用外部 javascript 库,不幸的是,并非每个库都经过良好的编程,所以除非您完全控制网页中包含的所有库,或者至少您知道它们处理得很好,否则请避免这种情况.
  • @JustinJohnson:在 ES5 中有一个妥协:defineProperty(…, {enumerable:false})。还有uid 方法本身should be in the Object namespace anyway
  • @JustinJohnson 如果让我们说对象 id 是 4,那么你将如何将 id 为 4 的 obj 分配给一个变量,以便你可以用它做一些事情......比如访问它的属性?跨度>
【解决方案2】:

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

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

【讨论】:

  • 为什么不return currentId
  • 为什么是WeakMap 而不是Map?如果您提供字符串或数字,该函数将崩溃。
  • @NateSymer 通过使用 Map,键持有对传递给它的对象的强引用。这可以防止它们被垃圾收集并且是主要的内存泄漏
【解决方案3】:

最新的浏览器提供了一种更简洁的方法来扩展 Object.prototype。此代码将使属性对属性枚举隐藏(for p in o)

对于browsers that implement 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

【讨论】:

  • “最新浏览器”显然不包括 Firefox 3.6。 (是的,我选择退出 Firefox 较新版本的升级竞赛,而且我确信我不是唯一一个。此外,FF3.6 才 1 岁。)
  • 1 岁并不是这项运动的“唯一”。网络是动态发展的——这是一件好事。现代浏览器具有自动更新程序正是为了实现这一点。
  • 几年后,这对我来说似乎比接受的答案更好。
【解决方案4】:

其实你不需要修改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;
}

【讨论】:

  • nocase、snake_case camelCase 将这三者都纳入了 6 行代码 sn-p。你不会每天都看到这种情况
  • 这似乎比object hacks 更有说服力。您只需要在希望 OP 的对象匹配时运行它,并且在其余时间它不会妨碍您。
  • 但是如果你复制对象(例如 { …obj } )你会复制 obj.__obj_id 。关键是要防止这种情况发生。
【解决方案5】:

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

         }
    };

})();

【讨论】:

    【解决方案6】:

    @justin answer 的 Typescript 版本,与 ES6 兼容,使用符号来防止任何键冲突,并添加到全局 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));
    

    【讨论】:

      【解决方案7】:

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

      var id = $.data(object);
      

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

      id = elem[ expando ] = ++uuid;
      

      我建议你使用同样的方法,因为 John Resig 显然知道所有关于 JavaScript 的知识,他的方法基于所有这些知识。

      【讨论】:

      • jQuery 的data 方法有缺陷。参见例如stackoverflow.com/questions/1915341/…。此外,John Resig 并不知道所有关于 JavaScript 的知识,并且相信他知道对 JavaScript 开发人员没有帮助。
      • @Tim,它在获取唯一 ID 方面的缺陷与此处介绍的任何其他方法一样多,因为它在引擎盖下的作用大致相同。是的,我相信 John Resig 比我知道的多得多,即使他不是 Douglas Crockford,我也应该从他的决定中学习。
      • AFAIK $.data 不适用于 JavaScript 对象,仅适用于 DOM 元素。
      【解决方案8】:

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

      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
      

      【讨论】:

        【解决方案9】:

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

        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__ 是非标准的。
        【解决方案10】:

        尽管建议不要修改 Object.prototype,但在有限的范围内,这对于测试仍然非常有用。接受答案的作者更改了它,但仍在设置Object.id,这对我来说没有意义。这是一个可以完成这项工作的 sn-p:

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

        【讨论】:

          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

            这将为每个对象计算一个 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++);
              }
            })();
            

            【讨论】:

              【解决方案13】:

              如果您来这里是因为您像我一样处理类实例,您可以使用静态变量/方法通过自定义唯一 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 );
              }

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2010-10-19
                • 2013-05-31
                • 2023-01-13
                • 1970-01-01
                • 2014-05-17
                • 2019-05-27
                相关资源
                最近更新 更多