JavaScript对象ID

104

JavaScript对象/变量是否有某种唯一标识符?像Ruby有object_id一样。我不是指DOM id属性,而是某种内存地址。


4
您是否希望使用object_id来比较对象? - Upperstage
请参见以下链接:https://dev59.com/3nI-5IYBdhLWcg3wJEwK - Crescent Fresh
2
这绝对不是真的,问题已经在其他地方得到了回答。根本不接近。 - Erik Aronesty
@ErikAronesty 我同意。在链接的线程中,没有回答对象是否具有内置id的问题。 - BobRodes
5个回答

73

如果您想查找/关联一个具有唯一标识符的对象而不修改底层对象,您可以使用WeakMap

// Note that object must be an object or array,
// NOT a primitive value like string, number, etc.
var objIdMap=new WeakMap, objectCount = 0;
function objectId(object){
  if (!objIdMap.has(object)) objIdMap.set(object,++objectCount);
  return objIdMap.get(object);
}

var o1={}, o2={}, o3={a:1}, o4={a:1};
console.log( objectId(o1) ) // 1
console.log( objectId(o2) ) // 2
console.log( objectId(o1) ) // 1
console.log( objectId(o3) ) // 3
console.log( objectId(o4) ) // 4
console.log( objectId(o3) ) // 3

使用 WeakMap 代替 Map 可以确保对象仍然能够进行垃圾回收。


2
最佳答案!使用 O(n) 的内存,其中 n 是您关心的对象 ID 数量(而不是所有对象),并且不影响垃圾回收。 - Jason Cohen
太棒了!甚至在IE11上也能运行(在Win10上测试过)。 - Breaker222

56

不,对象没有内置标识符,但你可以通过修改对象原型来添加一个标识符。下面是一个如何实现的例子:

(function() {
    var id = 0;

    function generateId() { return id++; };

    Object.prototype.id = function() {
        var newId = generateId();

        this.id = function() { return newId; };

        return newId;
    };
})();

话虽如此,一般来说修改对象原型被认为是非常糟糕的做法。我建议您根据需要手动分配对象ID或像其他人建议的那样使用touch函数。


1
ActionScript 3中有一个Dictionary对象,它使用严格相等来进行键比较,因此可以使用对象作为键。在JavaScript中是否有相应的对象,或者您是否需要手动为每个对象构建唯一的ID(通过Object.prototype或手动添加ID选择对象)? - Triynko
不幸的是,JavaScript 没有类似的功能。听起来你将不得不给对象一个 ID,然后像你建议的那样使用这些 ID 作为对象中的键。话虽如此,如果你真的想聪明一点,你可以通过覆盖 toString 方法来实现 "id 对象",并像这样使用它们:id = new Id(); cache[id] = obj。这有点疯狂,但非常有趣。这是一篇我写的文章,更详细地解释了这个技巧:http://xavi.co/articles/fun-with-tostring-in-javascript - Xavi
好的,我刚刚找到原因了。jQuery也会覆盖ID,当我覆盖它时,我的页面就崩溃了。哈哈。好吧。所以,我只需要避免命名问题并祈祷好运。 - Lodewijk
2
这个例子可以作为闭包和原型继承的一个很好的使用示例。 - Peter Clause

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

8
请注意,如果您希望对象之后具有不同的标识符,这种方法将与大多数对象复制方式不兼容。 - Paul Draper
确实,您需要一个特殊的“copyObject”函数,专门考虑到这个__obj_id。您还必须确保在其他库中使用“__obj_id”时没有冲突。在ActionScript中,这要容易得多,因为其Dictionary对象在键上使用严格的相等比较,包括用作键的对象。实际上,您可能可以在JS中编写一个Dictionary类,该类会自动将此方式附加到作为键添加到其中的对象的ID。就像量子力学JavaScript ID一样;除非您尝试使用此函数观察它们,否则它们不存在,哈哈。 - Triynko

2

我刚刚看到这篇文章,想分享我的想法。正如其他人所建议的那样,我建议手动添加ID,但如果你真的想要接近你所描述的内容,你可以使用以下方法:

var objectId = (function () {
    var allObjects = [];

    var f = function(obj) {
        if (allObjects.indexOf(obj) === -1) {
            allObjects.push(obj);
        }
        return allObjects.indexOf(obj);
    }
    f.clear = function() {
      allObjects = [];
    };
    return f;
})();

您可以通过调用objectId(obj)来获取任何对象的ID。如果您希望该ID成为对象的属性,则可以扩展原型:

Object.prototype.id = function () {
    return objectId(this);
}

或者你可以通过添加类似的方法手动为每个对象添加一个ID来实现。

主要的注意点是这将阻止垃圾回收器在对象超出作用域时销毁它们...它们永远不会超出allObjects数组的作用域,因此您可能会发现内存泄漏是一个问题。如果您坚持使用此方法,应仅在调试目的时使用。需要时,您可以执行objectId.clear()来清除allObjects并让GC完成其工作(但从那时起,所有对象ID都将被重置)。


我认为这是一种缓慢但稳健的解决方案,可以稍作改进:var objectId = (function() { var mem = []; var f = function(obj) { var r = mem.indexOf(obj); if (r === -1) { r = mem.length; mem.push(obj); } return r; }; f.reset = function() { return mem = []; }; return f; })(); - luochen1990
@luochen1990,我觉得你会对它的速度感到惊讶。(但你说得对,最好将indexOf()的结果放在一个变量中,尽管我认为这是从DRY的角度而不是优化的角度来考虑的。)只要GC问题能够有效地管理,我认为你必须拥有大量的对象才能注意到任何显着的性能影响。 - Nathan MacInnes

0

const log = console.log;

function* generateId() {
  for(let i = 0; ; ++i) {
    yield i;
  }
}

const idGenerator = generateId();

const ObjectWithId = new Proxy(Object, {
  construct(target, args) {
    const instance = Reflect.construct(target, args);
    instance['id'] = idGenerator.next().value;
    return instance;
  }
})

const myObject = new ObjectWithId({
  name: '@@NativeObject'
});

log(myObject.id);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接