如何获取JavaScript对象的引用或引用计数?

73

如何获取对象的引用计数

  • 能否确定一个JavaScript对象是否有多个引用
  • 或者它是否有除我访问时的引用外的其他引用
  • 甚至只是获取引用计数本身
  • 我能否从JavaScript本身中找到这些信息,还是需要跟踪自己的引用计数器。

显然,我的代码访问该对象时,必须至少存在一个引用。但是我想知道的是是否还有其他对它的引用,或者我的代码是唯一访问它的地方。如果没有其他地方引用它,我希望能够删除该对象。

如果您知道答案,无需阅读此问题的其余部分。下面只是一个示例,以使事情更加清晰。


用例

在我的应用程序中,我有一个名为contactsRepository对象实例,其中包含所有联系人的数组。还有多个Collection对象实例,例如friends集合和coworkers集合。每个集合都包含来自contacts Repository的不同数据项集合。

示例代码

为了使这个概念更加具体,考虑下面的代码。每个Repository对象实例都包含特定类型的所有项列表。您可能有一个联系人Contacts的存储库和一个独立的事件Events的存储库。为了保持简单,您可以只获取、添加和删除项目,并通过构造函数添加许多项目。

var Repository = function(items) {
  this.items = items || [];
}
Repository.prototype.get = function(id) {
  for (var i=0,len=this.items.length; i<len; i++) {
    if (items[i].id === id) {
      return this.items[i];
    }
  }
}
Repository.prototype.add = function(item) {
  if (toString.call(item) === "[object Array]") {
    this.items.concat(item);
  }
  else {
    this.items.push(item);
  }
}
Repository.prototype.remove = function(id) {
  for (var i=0,len=this.items.length; i<len; i++) {
    if (items[i].id === id) {
      this.removeIndex(i);
    }
  }
}
Repository.prototype.removeIndex = function(index) {
  if (items[index]) {
    if (/* items[i] has more than 1 reference to it */) {
      // Only remove item from repository if nothing else references it
      this.items.splice(index,1);
      return;
    }
  }
}  

注意remove中的那行代码注释。我只想在没有其他对象引用该项的情况下,从我的主存储库中删除该项。以下是Collection

var Collection = function(repo,items) {
  this.repo = repo;
  this.items = items || [];
}
Collection.prototype.remove = function(id) {
  for (var i=0,len=this.items.length; i<len; i++) {
    if (items[i].id === id) {
      // Remove object from this collection
      this.items.splice(i,1);
      // Tell repo to remove it (only if no other references to it)
      repo.removeIndxe(i);
      return;
    }
  }
}

然后这段代码使用 RepositoryCollection

var contactRepo = new Repository([
    {id: 1, name: "Joe"},
    {id: 2, name: "Jane"},
    {id: 3, name: "Tom"},
    {id: 4, name: "Jack"},
    {id: 5, name: "Sue"}
  ]);

var friends = new Collection(
  contactRepo,
  [
    contactRepo.get(2),
    contactRepo.get(4)
  ]
);

var coworkers = new Collection(
  contactRepo,
  [
    contactRepo.get(1),
    contactRepo.get(2),
    contactRepo.get(5)
  ]
);

contactRepo.items; // contains item ids 1, 2, 3, 4, 5 
friends.items;  // contains item ids 2, 4
coworkers.items;  // contains item ids 1, 2, 5

coworkers.remove(2);

contactRepo.items; // contains item ids 1, 2, 3, 4, 5 
friends.items;  // contains item ids 2, 4
coworkers.items;  // contains item ids 1, 5

friends.remove(4);

contactRepo.items; // contains item ids 1, 2, 3, 5 
friends.items;  // contains item ids 2
coworkers.items;  // contains item ids 1, 5
注意观察coworkers.remove(2)没有从contactRepo中删除id为2的内容。这是因为它还被friends.items引用着。然而,friends.remove(4)会将id为4的内容从contactRepo中删除,因为没有其他集合在引用它。
总结: 以上是我想要做的事情。我相信可以通过跟踪自己的引用计数等方式完成,但如果有使用JavaScript内置的引用管理的方法,我想知道如何使用它。

如果删除是您唯一的目的,那么垃圾回收器就是这项任务的最佳选择。它将/应该会完全按照您所描述的执行。 - Itay Moav -Malimovka
1
@Itay:当没有Collection实例引用它时,我想从Repository中删除一个项目。因此,常规的垃圾回收在这里不起作用,因为该项目仍然存在于Repository中。我必须知道对象是否仍在某个地方使用,这就是为什么我创建了一个存储所有正在使用的对象引用的存储库对象。 - Tauren
然后,您需要编写一个全局函数来替换=运算符,并在其中构建一个...引用表?您还需要编写某种垃圾收集器。所有这些都可以在JS中完成。尽管我想知道是否真的有必要做到这一点。如果您“忘记了对象”,那又关您什么事呢? - Itay Moav -Malimovka
@Itay:谢谢,我没有考虑过重载=,但不确定我会走这条路。我意识到这可能听起来过于复杂,但我有一个要求,即无论在UI上显示数据元素的位置如何,都必须准确反映实际值。因此,如果同一联系人显示3次,如果发生更改,则其他联系人也必须立即更改。我正在构建一个事件驱动系统来完成这项工作,但我发现需要确保所有UI元素绑定到同一数据模型,并且如果我可以跟踪引用计数,那将是最容易的。 - Tauren
你的问题非常重要,有人知道任何可以显示这种信息的调试器吗?Chrome调试器和Firebug都不行。在调试器上拥有它比完全不知道要好。 - Hoffmann
2个回答

21

不行,如果你真的需要计算引用,你将不得不手动执行操作。JS 没有接口来访问 GC 或弱引用。

虽然你可以实现一个手动引用计数对象列表,但是这是否值得所有额外的开销(包括性能方面,更重要的是代码复杂性)还值得怀疑。

在你的示例代码中,似乎更简单的方法是忘记 Repository,使用普通的 Array 来存储列表,并让标准的垃圾回收机制负责清除未使用的对象。如果你需要获取所有正在使用的人的列表,则只需要将 friendscoworkers 列表进行 concat 操作(如果需要,可以对它们进行排序/去重)。


3
感谢您的反馈。我担心没有一种原生的JavaScript方法可以做到这一点。我意识到这可能看起来有很多冗余代码,但我正在处理一个非常大的应用程序,并且这只是一个过度简化的例子。在应用程序中,跟踪所有不同的friendscoworkers和其他相关数组几乎是不可能的。此外,不仅仅是联系人,还有许多不同类型的对象。因此,为每种类型都有一个存储库实际上会让事情变得更简单。所有这些都与数据绑定解决方案有关,它会在模型更改时触发事件。 - Tauren
1
你知道是否有任何方法可以在调试器(如Firebug或Chrome Web Inspector)中获取引用计数吗? - Hoffmann
"JS没有与此相关的接口,GC或弱引用。 "我怀疑。WeakRef, FinalizationRegistry。" - Константин Ван

1
你可能对reduce函数和array.map函数感兴趣。map可用于帮助确定集合的交集,或是否存在交集。用户定义的reduce函数可以像合并一样使用(有点像覆盖加法运算符,以便您可以将操作应用于对象,或者根据您定义的reduce函数合并所有集合的“id”,然后将结果分配给主参考数组,建议保留一个影子数组,其中包含所有根对象/值,以防您想要回滚或其他情况)。注意:在减少对象或数组时必须小心原型链。在这种情况下,map函数将非常有帮助。
我建议不要删除存储在Repository中的对象或记录,因为您可能稍后还需要引用它。我的方法是创建一个ShadowRepository,该仓库反映了至少具有一个“Reference”的所有记录/对象。从您在此处呈现的描述和代码来看,似乎您正在初始化所有数据并存储对1、2、4、5的引用。
var contactRepo = new Repository([
    {id: 1, name: "Joe"},
    {id: 2, name: "Jane"},
    {id: 3, name: "Tom"},
    {id: 4, name: "Jack"},
    {id: 5, name: "Sue"}
]);
var friends = new Collection(contactRepo,[
    contactRepo.get(2),
    contactRepo.get(4)
]);

var coworkers = new Collection(contactRepo,[
    contactRepo.get(1),
    contactRepo.get(2),
    contactRepo.get(5)
]);

从存储库和集合的初始化开始,你所询问的“如果没有引用,则从存储库中删除项目”第三个问题需要立即删除。但是,你可以通过几种不同的方式跟踪引用。
我考虑使用Object.observe处理类似情况。但是,Object.observe不适用于所有浏览器。我最近转向了WatchJS
我正在努力理解Watch.JS背后的代码,以允许在对象上创建动态观察者列表,这样就可以删除不再监视的项,尽管我建议在访问点处删除引用。我的意思是,与公开了其同级的记录/项/属性/对象的对象共享即时词法作用域的变量可以被删除,使其在公开底层数据的对象之外不再可访问。我为起始引用生成唯一ID,以避免意外重复使用相同的ID。
感谢您分享您的问题和使用的结构,这帮助我考虑到一个我自己的特定情况,在那里我正在生成对词汇兄弟姐妹的唯一标识引用,这些唯一的ID被保留在具有范围的一个对象上。在阅读这里之后,我已经重新考虑并决定仅公开一个引用,然后将该引用分配给变量名,无论在哪里需要,例如在创建观察程序或其他集合中。

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