在JavaScript中是否可能创建“弱引用”?

104

仅供参考:JavaScript没有这个功能,但ActionScript 3(也是ECMAScript)有。请查看Dictionary的构造函数参数 - Amir
4
弱引用正在讨论是否要加入 ES6 中,敬请关注。 - Ryan Smith
2
官方规范维基/讨论位于http://wiki.ecmascript.org/doku.php?id=strawman:weak_refs,目前“最后修改时间:2013/02/02 22:25” 一些其他规范讨论在https://esdiscuss.org/topic/what-is-the-status-of-weak-references,目前的最后一篇帖子为“Sun Mar 3 11:56:05 PST 2013” - Destiny Architect
在大多数情况下,WRs是试图解决Lapsed Listener Problem的一种尝试,这里进行了讨论:[https://dev59.com/T6Hia4cB1Zd3GeqPTGvQ]。如果那个问题有一个好的答案,我认为就不需要WRs了。 - James
@supercat 我已经发布了对于“过期的监听器问题”的回答(https://dev59.com/T6Hia4cB1Zd3GeqPTGvQ#51911345)。 - James
11个回答

43

更新:自2020年7月起,一些实现(Chrome、Edge、Firefox和Node.js)已经支持WeakRefs,这是在2020年12月16日的“Stage 3 Draft”中定义的WeakRefs proposal

JavaScript没有对弱引用提供语言支持。您可以使用手动引用计数来自己编写,但这并不是特别顺畅。您无法创建代理包装对象,因为在JavaScript中,对象永远不知道它们即将被垃圾回收。

因此,您的“弱引用”成为简单查找中的键(例如整数),具有添加引用和删除引用方法,当没有手动跟踪的引用时,条目可以被删除,从而使将来在该键上的查找返回null。

这不是真正的弱引用,但它可以解决一些相同的问题。它通常在复杂的Web应用程序中使用,以防止浏览器(通常是IE,特别是旧版本)中存在DOM节点或事件处理程序与与其关联的对象(如闭包)之间的引用循环时发生内存泄漏。在这些情况下,甚至可能不需要完整的引用计数方案。


2
我没有仔细检查(或使用)代码,但是es-lab有一个脚本提供基本的WeakMap模拟Aurora 6 (Mozilla)有一个非标准的WeakMap实现 - theazureshadow
2
使用ES6,这个答案已经不正确了。请看下面的我的答案 https://dev59.com/gnVC5IYBdhLWcg3whRgw#28567560 - thelastshadow
9
这仍然是正确的,因为ES6 WeakMaps不是真正的弱引用。WeakMaps只接受对象作为键,并且对这些对象的引用被弱地保持。请参见http://stackoverflow.com/questions/32397729/partial-garbage-collection-of-objects-possible-server-side-js - CodeManX
我写了一个类来模拟弱映射,并在这里发布了它:https://dev59.com/zl8e5IYBdhLWcg3wnbVj#47017206 - Ryan Shillington

15

13

更新:2019年9月

目前还不能使用弱引用,但很可能很快就可以了,因为JavaScript中的弱引用正在进行中。详情见下文。

提案

该提议现在处于第3阶段,这意味着它具有完整的规范,并且进一步的改进需要来自实现和用户的反馈。

WeakRef 提议包含两个主要的新功能:

  • 使用 WeakRef 类 创建指向对象的弱引用
  • 使用 FinalizationGroup 类 在垃圾回收后运行自定义终结器清理对象

使用情景

弱引用的主要用途是实现保持大对象不占用内存的缓存或映射,当大对象仅出现在缓存或映射中时,不希望其占用内存。

终结处理是执行代码以清理程序不能访问的对象。用户定义的终结处理程序可以启用多种新用途,并在管理垃圾收集器不知道的资源时有助于防止内存泄漏。

来源和更多阅读材料

https://github.com/tc39/proposal-weakrefs
https://v8.dev/features/weak-references


这是关于弱引用的IT技术相关内容,您可以查看以上两个链接来了解更多信息。

1
Firefox Nightly已经添加了对WeakRef的实验性支持。以下是一个使用它创建WeakSet可迭代版本的示例实现:https://gist.github.com/seanlinsley/bc10378fd311d75cf6b5e80394be813d - seanlinsley

5

3

3

2

真正的弱引用,目前还没有(但浏览器制造商正在研究此问题)。但是有一个想法可以模拟弱引用。

您可以构建一个缓存,通过它来驱动您的对象。当存储对象时,缓存会预测对象将占用多少内存。对于某些项目,例如存储图像,这很容易计算。对于其他项目,这可能更加困难。

当您需要一个对象时,然后向缓存请求它。如果缓存具有该对象,则返回该对象。如果不存在,则生成该项,存储并返回它。

当总预测内存达到一定水平时,缓存通过删除项目来模拟弱引用。它将根据检索频率和取出时间的权重预测最不常用的项。如果将创建该项的代码作为闭包传递给缓存,则还可以添加“计算”成本。这将允许缓存保留非常昂贵的构建或生成项目。

删除算法非常关键,因为如果做错了,就可能会删除最受欢迎的项目。这将导致可怕的性能问题。

只要缓存是存储对象的唯一对象,即具有永久引用,那么上述系统作为真正弱引用的替代方案应该可以很好地工作。


25
你所说的大部分内容与弱引用无关,对吗? - Erik Kaplun
22
@JL235,弱引用的重要用途不仅是用于缓存,而是用于事件处理程序。我有一些对象,在其存在期间,应该观察某些其他事件--但我不希望它在通知列表中的事实构成垃圾回收目的的引用。 - Michael Lorton
7
弱引用与缓存无关。弱引用意味着您想要跟踪某些东西,但如果没有剩余的引用指向被跟踪的对象,您允许其被删除。 - fabspro
8
使用弱引用构建缓存以实现自动过期,显然具有实际应用场景。 - Phil Freeman
5
缓存通常是弱引用的主要原因。事件处理程序DOM问题只是某些IE浏览器的错误。 - axkibe
显示剩余3条评论

2
使用缓存机制来模拟弱引用,就像JL235上面建议的那样,是合理的。如果弱引用本来存在,你会观察到这样的行为:
this.val = {};
this.ref = new WeakReference(this.val);
...
this.ref.get(); // always returns val
...
this.val = null; // no more references
...
this.ref.get(); // may still return val, depending on already gc'd or not

如果使用缓存,您会观察到:

this.val = {};
this.key = cache.put(this.val);
...
cache.get(this.key); // returns val, until evicted by other cache puts
...
this.val = null; // no more references
...
cache.get(this.key); // returns val, until evicted by other cache puts

作为一个引用的持有者,你不应该做任何关于它何时引用值的假设,这和使用缓存没有什么不同。

-1

提案和一些细节 https://github.com/tc39/proposal-weakrefs

Typescript 复制/粘贴版本

export class IterableWeakMap<T extends Object, V> {
  weakMap = new WeakMap();

  refSet = new Set<WeakRef<T>>();

  finalizationGroup = new FinalizationRegistry(IterableWeakMap.cleanup);

  static cleanup({ set, ref }: { set: Set<WeakRef<Object>>; ref: WeakRef<Object> }) {
    set.delete(ref);
  }

  constructor(iterable?: Iterable<[T, V]>) {
    if (!iterable) return;
    for (const [key, value] of iterable) {
      this.set(key, value);
    }
  }

  set(key: T, value: V) {
    const ref = new WeakRef<T>(key);

    this.weakMap.set(key, { value, ref });
    this.refSet.add(ref);
    this.finalizationGroup.register(key, { set: this.refSet, ref }, ref);
  }

  get(key: T) {
    const entry = this.weakMap.get(key);
    return entry && entry.value;
  }

  delete(key: T) {
    const entry = this.weakMap.get(key);
    if (!entry) {
      return false;
    }

    this.weakMap.delete(key);
    this.refSet.delete(entry.ref);
    this.finalizationGroup.unregister(entry.ref);
    return true;
  }

  *[Symbol.iterator]() {
    for (const ref of this.refSet) {
      const key = ref.deref();
      if (!key) continue;
      const { value } = this.weakMap.get(key);
      yield [key, value];
    }
  }

  entries() {
    return this[Symbol.iterator]();
  }

  *keys() {
    for (const [key] of this) {
      yield key;
    }
  }

  *values() {
    for (const [, value] of this) {
      yield value;
    }
  }
}

-5

EcmaScript 6(ES Harmony)有一个WeakMap对象。现代浏览器的支持情况相当不错(最近3个版本的Firefox,Chrome甚至即将推出的IE版本都支持它)。


30
这并不完全相同。WeakMap 不会提供对象的弱引用——在 WeakMap 中,弱引用是针对 而非 的。弱引用存在于该映射中只是为了防止内存泄漏,否则用户无法观察到它们。请注意,不要改变原文的意思。 - EyasSH
1
你说得对,弱引用的是键而不是值。但使用弱引用的整个目的是允许垃圾回收所引用的对象。OP发布了两个链接,第二个链接是关于在无法扩展对象时添加id的,实际上它推荐使用WeakHashMap,这是Java中类似于JavaScript的WeakMap。 - thelastshadow
12
祝你好运使用WeakMap来实现弱引用,因为weakmap.get(new String('任何可能已经存在或将来会存在的键'))将始终为undefined。这是不有用的。 下投票! - user3338098

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